These notes describe the process of creating the buffers necessary to render the low level primitives (triangles) using Direct3D. Before reading this page you should have read the notes on flexible vertex formats - FVF
Each triangle has 3 vertices. A vertex can contain different information (it always has a position) and the type of information it holds is up to you and defined in your own structure. These notes assume a CUSTOM_VERTEX vertex structure is used with an FVF called D3DF_CUSTOMVERTEX.
As usual we declare a pointer to the vertex buffer Direct3D object. We then call an API function to create an instance of the vertex buffer object and point our pointer at it.
LPDIRECT3DVERTEXBUFFER9 m_vb;
HRESULT CreateVertexBuffer(UINT length, DWORD usage, DWORD FVF, D3DPOOL pool, IDirect3DVertexBuffer9** vertexBuffer, HANDLE* handle );
E.g. to create a vertex buffer to hold 4 vertices of our custom vertex type we would write:
HRESULT hr=gD3dDevice->CreateVertexBuffer( 4*sizeof(CUSTOMVERTEX),
D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
D3DPOOL_MANAGED, &m_vb, NULL );
Remember to check the return code for success. If it fails you will get a reason shown in the Viz output pane (as long as you run using F5 and not the exclamation mark in Viz and have Direct3D set to debug mode).
When creating the vertex buffer you specify how many vertices will be held. You can now fill in that number of vertices. Because vertex buffers can often be held in video card or AGP memory you need to lock the vertex buffer before you can read or write to it. Note: doing this a lot at render time can cause slow down as it stalls the video card, so try to only do it once when initialising. If you must do it dynamically each render loop do it quickly. Lock the buffer, copy the data and unlock as fast as you can.
The lock call returns a pointer to the vertex buffer memory area:
HRESULT Lock( UINT OffsetToLock, UINT SizeToLock, VOID **data, DWORD Flags );
So using our example again, to lock the buffer we would:
CUSTOMVERTEX* pVertices;
m_vb->Lock( 0, 0, (void**)&pVertices, 0 ));
The only difficult thing above is the way we convert our custom vertex pointer to the required pointer for the call. The call requires a void pointer so we have to specify void** to convert it. Note: any pointer can be converted to a void pointer. A void pointer is a handy thing (but dangerous) as it can be used to point to anything - you do not have to specify a type.
You now have a pointer to the vertex data. You can use this like an array. e.g. to set the position:
pVertices[0].pos.x=10.0f;
pVertices[0].pos.y=20.0f;
pVertices[0].pos.z=30.0f;
Once you have finished writing your data you must unlock the vertex buffer, this is simple:
m_vb->Unlock();
To specify which of the vertex in the vertex buffer make up each triangle you need to create and fill an index buffer. So in the above example if it defines the 4 vertex around a square like this:
To draw the two triangles shown above we will need to specify which vertex are used for which triangle:
Triangle 0 (green one) uses vertex V0, V1 and V3
Triangle 1 (blue one) uses vertex V3, V1 and V2
Note: it is essential that you define the vertex in a clockwise rotation. DirectX uses the left handed co-ordinate system.
Our index buffer is just one big array with every 3 entries representing the vertex used in that triangle. So for the above the vertex buffer will look like this:
0,1,3,3,1,2
Note that for 2 triangles we have 6 entries. The number of entries in an index buffer must always be 3 times the number of triangles.
As before we declare a pointer to an index buffer object and call an API function that creates the object and points our pointer at it.
LPDIRECT3DINDEXBUFFER9 m_ib;
HRESULT CreateIndexBuffer( UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** IndexBuffer, HANDLE* Handle );
An example of creating an index buffer might be:
gD3dDevice->CreateIndexBuffer(6*3*2,D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_ib, NULL)
As with the vertex buffer to fill an index buffer we must lock it, write our data and unlock it. The parameters are the same so an example only is given here:
WORD *indices=NULL;
m_ib->Lock( 0, 0, (void**)&indices, 0 );
After this call the indices pointer points to the memory area where the indices are held. We can write to this area using array notation. So to specify the vertex for our two triangles we would do this:
indices[0]=0;
indices[1]=1;
indices[2]=3;
indices[3]=3;
indices[4]=1;
indices[5]=2;
We must then unlock the buffer:
m_ib->Unlock();
Now that we have seen how to create vertex and index buffers the next step is to render their contents. See the next notes on rendering the triangles: Rendering Steps