Wazoo Enterprises

Having fun in game development!

Archive for February 4th, 2008


Rendering Geometry : Vertex Buffers

At this point in our sample codebase, we should be at a point where we’re ready to tackle something a little stronger
than displaying text, or logging an event to a file; let’s draw a triangle!

Vertex Buffers

The only mechanism that Direct3D employs to draw (aka “render”) geometry to the video hardware is through the use of
Vertex Buffers. In an abstract way, these are storage areas (similar to arrays) in either the memory on-board the video hardware
or in the memory on the computer. On a driver level, they are fashioned to maximize the performance of your
machine’s configuration by dropping the vertex buffers in the most optimal location it can find. In short, Vertex
Buffers contain the vertex data of the geometry you wish to render to the Direct3D Device. This vertex data can be
a vertex of any type; lit or unlit, transformed or untransformed. How is this vertex flexibility achieved?

Flexible Vertex Format (FVF)

The Flexible Vertex Format (FVF) that you must define along with your vertex buffer, allows you to drop them into
almost any 3D engine configuration / design that you can envision. The FVF “code” is a description to Direct3D of
what kind of vertices your vertex buffer contains. Check the Direct3D documentation for the different
FVF flags available, as we’ll only list the few most commonly used:

  • D3DFVF_DIFFUSE - The vertex format includes a diffuse color component
  • D3DFVF_NORMAL - The vertex format includes a normal vector (used for calculations such as lighting)
  • D3DFVF_XYZ - Vertex format includes the position (x,y,z) of an untransformed vertex
  • D3DFVF_XYZRW - Vertex format includes the postion (x,y,z) of a transformed vertex
  • D3DFVF_TEX0 - Vertex format includes texture coordinate information

To help illustrate more about using vertex buffers, let’s take a look at the code in our sample:

MyApp.h

// A structure for our custom vertex type
struct MY2DVERTEX
{
	FLOAT x, y, z, rhw; // The transformed position for the vertex
	DWORD color;        // The vertex color
};

Here we define our vertex MY2DVERTEX that we will use to render our scene geometry. We have an (x,y,z) position component
followed by an rhw value, which provides a clue that our vertices will contain transformed vertices. Finally,
we want to have a bit of color in our scene, so each vertex will contain a color component.

// Our custom FVF, which describes our custom vertex structure
#define D3DFVF_MY2DVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)

So we are creating a label D3DFVF_MY2DVERTEX which will “expand” to the compiler to use the FVF flags of
D3DFVF_XYZRHW|D3DFVF_DIFFUSE.

class MyApp : public CD3DApplication
{
protected:
	//snip..

	LPDIRECT3DVERTEXBUFFER8 m_pVB; //Vertex Buffer handle

	//snip..

We have defined an interface to our vertex buffer with the help of the LPDIRECT3DVERTEXBUFFER8 declaration.
For the most part, once we create a vertex buffer and load in the vertex data that we need, the Direct3DDevice will automatically
take care of things and store the necessary data.

HRESULT MyApp::InitDeviceObjects()
{
	//snip...

	//create our vertex buffer
	// Initialize three vertices for rendering a triangle
	MY2DVERTEX ourVertices[] =
	{
		{ 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color
		{ 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },
		{  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, },
	};

	// Create the vertex buffer. Here we are allocating enough memory
	// (from the default pool) to hold all our 3 custom vertices. We also
	// specify the FVF, so the vertex buffer knows what data it contains.
	if( FAILED( m_pd3dDevice->CreateVertexBuffer( 3*sizeof(MY2DVERTEX), //3 vertices in the buffer
		0,
		D3DFVF_MY2DVERTEX, //the FVF flags for this vertex buffer
		D3DPOOL_DEFAULT,   //should always use this flag to ensure the device properly manages this buffer
		&m_pVB ) ) )       //vertex buffer interface to create
	{
		return E_FAIL;
	}

	// Now we fill the vertex buffer. To do this, we need to Lock() the VB to
	// gain access to the vertices. This mechanism is required because vertex
	// buffers may be in device memory.
	VOID* pVertices;
	if( FAILED( m_pVB->Lock( 0, sizeof(ourVertices), (BYTE**)&pVertices, 0 ) ) )
	{
		FileLogger::getSingletonPtr()->logError(”MyApp”,”Error locking down vertex buffer!”);
		return E_FAIL;
	}

        //easiest way is to memcpy the vertex data from our array into the vertex buffer
	memcpy( pVertices, ourVertices, sizeof(ourVertices) );

	//don’t forget to unlock access to the buffer!
	m_pVB->Unlock();

First, we’re defining an array of MY2DVERTEX information which is describing 3 vertices that we want to draw in our
scene. Because we’re only working with 3 vertices for this scene, we are aiming to render a simple triangle. Once we’ve
defined our vertex data, we go through the work of creating an actual vertex buffer to contain it. Using the CreateVertexBuffer
method available via the Direct3DDevice interface, we specify the parameters necessary to define what type of vertex
information we want to store. Next we need to take the step of filling the newly created vertex buffer with our
vertex data. In order to do this, we have to signal the IDirect3DVertexBuffer8 interface that we want to initiate a
Lock on the buffer in order to work with it. Once we obtain a valid pointer to the location in memory of our
vertex buffer, we can perform a simple memcpy to copy the vertex data from our ourVertices array into
the vertex buffer. Once finished, we HAVE to unlock the access to the vertex buffer, otherwise our video
performance is severly crippled!

Drawing the Geometry

Finally we get to the meat of this tutorial: the actual rendering of our vertex data! Let’s waste no time and jump
right to the code:

	//snip!

	// Begin the scene
	if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
	{
		m_pd3dDevice->SetStreamSource( 0, m_pVB, sizeof(MYVERTEX) );
		m_pd3dDevice->SetVertexShader( D3DFVF_MYVERTEX );
		m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );

		//snip!

That’s it! We first signal to the Direct3DDevice the source location of our vertex data information by using SetStreamSource.
Next, we setup the vertex shader FVF we want to use, and finally we use the DrawPrimitive method to blast our
single triangle to the device!

Feedback? Comments?

Either comment to this posting, or use our contact form to get in touch with us.

Additional Resources

Downloads

dx81-RenderingGeometry.zip