Greg Dolley’s Weblog

A Blog about Graphics Programming, Game Programming, Tips and Tricks

Archive for February 28th, 2008

DirectX 9 C++ Graphics Tutorial 2: Drawing a Triangle

Posted by gregd1024 on February 28, 2008

In this tutorial we’re going to look at how to draw a stationary triangle using DirectX 9 and C++. We’ll be building off of the concepts taught in the first tutorial (DirectX 9 C++ Graphics Tutorial 1: Getting Started). Most of the code will be the same since it involves setting up a form and initializing DirectX.

Requirements

The tutorial sample code was built and tested with Visual Studio Express 2008. However, using the code “as-is” in Visual Studio 2005 should work too.

You’ll need the DirectX 9 SDK for compiling the sample code. Use this link for downloading: click here.

Tutorial Source and Project Files

Download the project files, binaries, and source with this link:

Getting Started

I’m not going to cover the Win32 initialization code or how to initialize DirectX in this tutorial. For that, see the previous tutorial: DirectX 9 C++ Graphics Tutorial 1: Getting Started.

The only difference between this tutorial’s code versus the last version is how we handle WM_PAINT messages. Previously, we simply cleared the color buffer and displayed it on the screen. This time we’re going to define three triangle vertices, create a vertex buffer, send that vertex buffer to DirectX, and finally tell DirectX to render the triangle.

Defining the Vertices

In DirectX there is no pre-defined vertex type or object. You have to make your own struct (or class) and then tell DirectX about its format via FVF (Flexible Vertex Format) codes. FVF codes are a set of constants that describe the contents and size of a vertex structure. For example, the constant D3DFVF_XYZ describes your structure as having three float variables representing an untransformed vertex; the constant D3DFVF_DIFFUSE describes a single DWORD value representing a diffuse color component in ARGB order. You can (and often will) combine a set of FVF code together. For instance, “D3DFVF_XYZ|D3DFVF_DIFFUSE” means your structure has three float variables followed by one DWORD variable. The correlation between constant and vertex layout is clearly defined in the DirectX SDK documentation.

In our sample program we used the following structure:

struct D3DVERTEX
{
   float x, y, z, rhw;
   DWORD color;
};

The (x, y, z, rhw) combination describe the transformed position of a vertex. The “color” member describes its diffuse color in the format of ARGB (Alpha, Red, Green, Blue). To describe this structure we use the following FVF code:

  • “D3DFVF_XYZRHW|D3DFVF_DIFFUSE” – transformed position with color info.

Let’s look at the code for handling the WM_PAINT event where it actually creates the vertices:

case WM_PAINT:

  // setup vertex information

  struct D3DVERTEX {float x, y, z, rhw; DWORD color;} vertices[3];

 

  vertices[0].x = 50;
  vertices[0].y = 50;
  vertices[0].z = 0;
  vertices[0].rhw = 1.0f;
  vertices[0].color = 0x00ff00;

 

  vertices[1].x = 250;
  vertices[1].y = 50;
  vertices[1].z = 0;
  vertices[1].rhw = 1.0f;
  vertices[1].color = 0x0000ff;

 

  vertices[2].x = 50;
  vertices[2].y = 250;
  vertices[2].z = 0;
  vertices[2].rhw = 1.0f;
  vertices[2].color = 0xff0000;

In this code we really just define a vertex array (“vertices[3]”) and fill in the values for a triangle.

Creating the Vertex Buffer

Next, we tell DirectX about our vertex data by creating a vertex buffer object. Here’s the code to do it (this code comes directly after the code in the last section):

LPDIRECT3DVERTEXBUFFER9 pVertexObject = NULL;
void *pVertexBuffer = NULL;

 

if(FAILED(g_pDirect3D_Device->CreateVertexBuffer(3*sizeof(D3DVERTEX), 0,
          D3DFVF_XYZRHW|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pVertexObject, NULL)))
   return(0);

if(FAILED(pVertexObject->Lock(0, 3*sizeof(D3DVERTEX), &pVertexBuffer, 0)))
   return(0);

memcpy(pVertexBuffer, vertices, 3*sizeof(D3DVERTEX));
pVertexObject->Unlock();

The first two lines just declare the pointers we’re going to use. The next line calls a DirectX function called CreateVertexBuffer(). This function allocates a vertex buffer object which we’ll use for all buffer operations.

CreateVertexBuffer() takes six parameters. The first parameter tells DirectX the required size of the vertex buffer (in bytes). The second parameter specifies how the vertex buffer will be used – “0” being the default. The third parameter tells DirectX about the memory layout of each vertex (the FVF format). The fourth parameter says that you don’t care where memory is allocated. The fifth parameter is the address of a pointer to be filled with the vertex buffer object location. Lastly, the sixth parameter specifies a shared handle (don’t worry about this).

Now we use our newly created vertex buffer object and call its Lock() method. This call gives us a memory buffer (pointed to by pVertexBuffer) that we must copy our vertex data into. I know this seems strange – we already created our own vertex array, filled it with data, now we have to copy it somewhere else? Don’t ask why, this is just how DirectX works. The next line, with the call to memcpy(), does this copying process.

Finally, when we’re done with the copy, we have to tell DirectX that the data is ready to go. This is done via the Unlock() method of the vertex buffer object.

Rendering the Vertex Buffer

Now we’re ready to actually draw the scene! Check out the following code (again, this code comes directly after the last line of the previous section):

// clear background to black
g_pDirect3D_Device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

 

// render the actual scene
if(SUCCEEDED(g_pDirect3D_Device->BeginScene()))
{
   g_pDirect3D_Device->SetStreamSource(0, pVertexObject, 0, sizeof(D3DVERTEX));
   g_pDirect3D_Device->SetFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE);
   g_pDirect3D_Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
   g_pDirect3D_Device->EndScene();
}

 

g_pDirect3D_Device->Present(NULL, NULL, NULL, NULL);
pVertexObject->Release();

 

ValidateRect(hwnd, NULL);

First, we clear the background to black by calling Clear(). You might recognize this from the previous tutorial (it’s the exact same call, but the color is different).

Next we call BeginScene() – every 3D frame in DirectX begins with this call.

The next two lines set the stream source and vertex format. SetStreamSource() tells DirectX to take the vertex information from our vertex buffer object. The first parameter specifies the stream number and the second is a pointer to the vertex buffer object itself. The third parameter says there is no offset from the beginning of the stream to the vertex data (if there was, this value would be the number of bytes in between). Finally, the fourth parameter is the size, in bytes, of each vertex. SetFVF() sets the FVF code that describes our vertex format (see “D3DFVF” in the DirectX SDK documentation for all the possible code combinations and their corresponding formats).

Now we’re at DrawPrimitive(). This tells DirectX to actually draw something. The first parameter specifies what to draw – in our case, a list of triangles (actually, just one triangle since our list only contains one element). The second parameter says start from vertex zero (in some cases you may want to start from somewhere other than the beginning). And the last parameter tells DirectX how many primitives to draw (for us, just one triangle).

Once all the drawing code is executed, we must call EndScene(). All 3D scenes in DirectX end with this call.

Finally we call Present() to display everything on the screen and call Release() on our vertex buffer object (since it’s no longer needed).

ValidateRect() is from the first tutorial. It tells Windows that we’ve handled all of the drawing in this window and Win32 doesn’t need to do any further processing.

Program Output

All this code generates the following output:

directx_tutorial_2_cpp_output

Notice how the colors of each vertex are “smoothed” over the face of the triangle. This is the default behavior of DirectX (and even OpenGL). Since colors are only defined for the vertices and not the face of the polygon, DirectX interpolates the color in between. This interpolation gives you the gradient effect.

Conclusion

Now that’s definitely better than the last tutorial of simply drawing a solid color on a form. However, the triangle is technically still 2D. If it were a 3D triangle we could adjust the z coordinate at each vertex, giving them different depths, and watch the triangle get drawn in perspective. Currently that’s not possible with this code. In order to turn it into a “real” 3D triangle, we need a camera position, a transformation matrix, and a couple other components. We’ll be discussing all this in the next DirectX C++ tutorial.

However, for the very next post, I’ll go over the Managed DirectX version of this same stuff using C#.

-Greg Dolley

*Get new posts automatically! Grab the RSS feed here. Want email updates instead? Click here.

Advertisements

Posted in 3D Graphics, C++ Graphics, DirectX | 35 Comments »