Rendering Steps

Before reading this you should have read how to create your custom vertex, FVF and how to create and fill your vertex and index buffers, Buffers.

When we come to render our triangles we will have created a vertex buffer and filled it with all the details of each vertex. We will also have created an index buffer and filled it with the indices of each vertex used by each triangle. We now need to send this data down the rendering pipeline, it is sent down a stream.

Note: these examples use the Fixed Function Pipeline (FFP) for rendering our triangles. If required we could define parts of that pipeline ourselves using vertex or pixel shaders. This is beyond the scope of these notes, however it is worth noting that it is in this rendering section that you would specify your vertex or pixel shader and then follow a process very similar to that described here. I will add shader notes to this site soon as they have really taken over from the FFP and the FFP has been completely removed from Direct3D 10..

The Rendering Steps

  1. Specify the material we wish to use for the following triangles
  2. Specify the texture we wish to use (if we want one or NULL if not)
  3. Set the stream source to our vertex buffer
  4. Set the FVF we will be using
  5. Set the index buffer we will be using
  6. Call the required DrawPrimitive function

An example here will help, this is an example from a cube drawing graphic entity I have written. This cube is coloured by a material and uses no texture. It has 24 vertices and is made up of 12 triangles (2 per side). It uses a custom vertex that contains position and a normal (for lighting). This does not share vertex which is why there are 24 vertex defined (I need separate normals).

void CGfxEntityCube::Render()
{

   gD3dDevice->SetMaterial( &m_material );
   gD3dDevice->SetTexture(0,NULL);
   gD3dDevice->SetStreamSource( 0, m_vb,0, sizeof(CUBEVERTEX) );
   gD3dDevice->SetFVF( D3DFVF_CUBEVERTEX );
   gD3dDevice->SetIndices( m_ib);
  
   // draw a triangle list using 24 vertices and 12 triangles
   gD3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST,0,0,24,0,12); 

}

Note: since the vertex are never shared between triangles we could avoid using an index buffer completely and instead call the simpler DrawPrimitive function, but here I want to demonstrate the use of index buffers so have included one.

Description of Rendering Steps

1,2. Setting material and texture

These are simple calls just to indicate which material and texture you will use. You can actually set more than one texture if required by passing the texture stage into the SetTexture call and then set flags to indicate how the textures will be combined (multi-texturing) This, however, is beyond the scope of these notes.

For more information on materials see here: materials
For more information on textures see here: textures

3. Setting the stream

After setting our material and texture we need to specify the stream we are going to send our triangle data down. This is achieved using the SetStreamSource call:

HRESULT SetStreamSource(UINT StreamNumber,IDirect3DVertexBuffer9 *pStreamData,UINT OffsetInBytes,UINT Stride );

  • StreamNumber - specifies the stream index, just use 0 here. Using other streams is an advanced topic not covered here. It is used when you want to apply more than one vertex shader.
  • pStreamData - a pointer to your vertex buffer
  • OffsetInBytes - an advanced method of offsetting stream data. Always use 0 here.
  • Stride - the stride determines how many bytes there are in the stream between each vertex data. This is always the size of your vertex structure.

4. Setting the FVF

We need to tell Direct3D how our vertex structure was created. We have done that previously by combining flags into our own FVF define, see the FVF notes for more details. The reason we have to do this is that we are sending all our vertex down a stream, the stream does not know how big each piece of information is or how to interpret it. So by passing the FVF we indicate the make up of our custom vertex.

HRESULT SetFVF(DWORD fvf)

fvf - the definition of our fixed function vertex type.

5. Setting the Indices

If we are using an index buffer we need to tell Direct3D about it, this is simply:

HRESULT SetIndices( IDirect3DIndexBuffer9 *pIndexData );

  • pIndexData - a pointer to our index buffer

6. Rendering

Now that we have told the device all about our data and provided pointers to that data we can now ask for that data to be rendered. There are a two main functions available:

DrawPrimitive - use this if you don't need to include an index buffer
DrawIndexedPrimitive - use this if you want to include an index buffer

The parameters to each are much the same, we will look at the DrawIndexedPrimitive function here as this is the one you will use most often:

HRESULT DrawIndexedPrimitive( D3DPRIMITIVETYPE Type, INT BaseVertexIndex, UINT MinIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount );

  • Type - the type of primitive to render, you can choose between point lists, line lists, triangle lists, triangle strips or triangle fans. These are just different ways of representing your geometry. The strip and fan methods can often reduce the data you need to pass. For most cases you use triangle lists and pass: D3DPT_TRIANGLIST
  • BaseVertexIndex, MinIndex - you can render just a section of your vertex buffer by specifying in these variables which vertex index is the first. Normally these are simply 0
  • NumVertices - this is where you specify how many vertices in the VB you will be using
  • StartIndex - The first entry in the index buffer to use to render your triangles. Normally set this to 0
  • PrimitiveCount - How many triangles to draw.

Advanced Note

This function provides methods to allow you to just use sections of your vertex and index buffer. The reason for this is simply for speed. Normally the data for one 3D entity would fill one vertex and index buffer. So every entity maintains its own vertex and index buffer, one per entity. This is normally fine, however for maximum performance it is a good idea to avoid changing vertex and index buffers all the time - graphic cards like a big chunk of data they can work with and don't like changing chunks so much. So what advanced graphics programmers do is pack vertex buffers and index buffers with the data for as many entities as they can. To render each entity they then set the material and texture and use the above call to just render the correct section of each buffer. This is an advanced technique but explains the reason for the index and offset parameters.

Expanded Rendering Example

Here is an example for drawing a cube with the whole of the render function defined:

void CGfxEntityCube::Render(const D3DXVECTOR3 &pos)
{

    // Set our material
    gD3dDevice->SetMaterial( &m_material );

    // Set the matrix to convert from model space to world space
    D3DXMATRIXA16 matWorld
    D3DXMatrixTranslation(&matWorld,pos.x,pos.y,pos.z);
    gD3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

    gD3dDevice->SetStreamSource( 0, m_vb,0, sizeof(CUBEVERTEX) );
    gD3dDevice->SetFVF( D3DFVF_CUBEVERTEX );
    gD3dDevice->SetIndices( m_ib);

    // draw a triangle list using our 24 vertices and 12 triangles
    gD3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST,0,0,24,0,12); 

}

Final Rendering Points

So that's it for rendering primitives. Again remember to check return codes for errors. If it does not work check the debug output in Viz to see if Direct3D is passing you error messages (to get this you must have DirectX in debug mode and run your game using F5 (debug) and not the execute exclamation mark). Often it can be baffling when your code looks correct but you see nothing on the screen, there are a number of reasons this might happen so I have written some notes specifically to help you track down the problem: Invisible geometry

Further Reading

On this site: Materials, Textures, FVF, Buffers, Invisible geometry, Lighting, Matrices, Z Buffer
Other: DrawIndexedPrimitive Demystified



© 2004-2016 Keith Ditchburn