Vertex and Index Buffers

  CU_MDX_Buffers.zip (63.6 KiB, 2,653 hits)


  CUnitFramework.zip (101.1 KiB, 20,443 hits)

Extract the C-Unit Framework
inside the project (not solution) directory.

Previously, we stored geometry data in a user-defined pointer. While this is ok for little demos, in real applications, we would want to use vertex buffers because transfering data over the system bus is too slow. Using VertexBuffers allows the video card to store vertices in local video memory, which can be rendered quickly by the GPU. An IndexBuffer lets us reuse vertices that we have already defined. Take a quad for example. A quad is formed by two triangles. These two triangles share two vertices. Without an index buffer, we would have to specify six separate vertices even though two of the vertices are shared. With an index buffer, we would only have to specify four vertices and use indices to access them.

Vertex and Index Buffers

While saving 2 vertices is trivial, using IndexBuffers in larger meshes can save quite a bit of memory. In this tutorial, we will see the difference between using an index buffer and not using an index buffer to render a square.

///
/// This event will be fired immediately after the Direct3D device has been
/// created, which will happen during application initialization. This is the best
/// location to create Pool.Managed resources since these resources need to be
/// reloaded whenever the device is destroyed. Resources created  
/// here should be released in the OnDetroyDevice callback.
///

/// The Direct3D device
public override void OnCreateDevice( Device device )
{
// Define 6 vertex buffer
m_vb6 = new VertexBuffer( device, 6 * PositionColored.StrideSize, Usage.WriteOnly, PositionColored.Format, Pool.Managed, null );
GraphicsBuffer buffer = m_vb6.Lock( 0, 0, LockFlags.None );
buffer.Write( new PositionColored( –1.0f, –1.0f, 5.0f, Color.Red ) );
buffer.Write( new PositionColored( –1.0f,  1.0f, 5.0f, Color.Yellow ) );
buffer.Write( new PositionColored(  1.0f, –1.0f, 5.0f, Color.Green ) );
buffer.Write( new PositionColored( –1.0f,  1.0f, 5.0f, Color.Violet ) );
buffer.Write( new PositionColored(  1.0f,  1.0f, 5.0f, Color.Blue ) );
buffer.Write( new PositionColored(  1.0f, –1.0f, 5.0f, Color.Orange ) );
m_vb6.Unlock();
buffer.Dispose();

// Define 4 vertex buffer
m_vb4 = new VertexBuffer( device, 4 * PositionColored.StrideSize, Usage.WriteOnly, PositionColored.Format, Pool.Managed, null );
buffer = m_vb4.Lock( 0, 0, LockFlags.None );
buffer.Write( new PositionColored( –1.0f, –1.0f, 5.0f, Color.Red ) );    // 0
buffer.Write( new PositionColored( –1.0f,  1.0f, 5.0f, Color.Yellow ) ); // 1
buffer.Write( new PositionColored(  1.0f, –1.0f, 5.0f, Color.Green ) );  // 2
buffer.Write( new PositionColored(  1.0f,  1.0f, 5.0f, Color.Blue ) );   // 3
m_vb4.Unlock();
buffer.Dispose();

// Define indices
ushort[] indices = { 0, 1, 2, 1, 3, 2 };
m_ib4 = new IndexBuffer( device, 6 * sizeof( ushort ), Usage.WriteOnly, Pool.Managed, true, null );
GraphicsBuffer<ushort> iBuffer = m_ib4.Lock<ushort>( 0, 0, LockFlags.None );
iBuffer.Write( indices );
m_ib4.Unlock();
iBuffer.Dispose();
}

First, we’ll define the square that does not use an index buffer. We start by creating a new VertexBuffer instance. To fill up the buffer, we need to get its GraphicsBuffer by calling VertexBuffer.Lock. Once we have the GraphicsBuffer, we fill it up with the 6 PositionColored vertices. Once the buffer is filled up, we need to unlock the VertexBuffer by calling VertexBuffer.Unlock.

Defining the square that will use an index buffer follows the same process. Notice we only specify 4 vertices instead of 6. Creating the IndexBuffer is similar to the process of creating the VertexBuffer. We define an array of indices and fill up the IndexBuffer’s GraphicsBuffer. Take note here that some video cards only support 16-bit indices. Newer cards will support 32-bit indices, so if you want to support older cards, use 16-bit indices.

/// Renders the current frame.
/// The Direct3D device
/// Time elapsed since last frame
public override void OnRenderFrame( Device device, float elapsedTime )
{
device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0 );
device.BeginScene();

// Clip…

// Render buffers
device.VertexFormat = PositionColored.Format;
device.Transform.World = Matrix.Translation( –1.5f, 1.0f, 0.0f );
device.SetStreamSource( 0, m_vb6, 0, PositionColored.StrideSize );
device.DrawPrimitives( PrimitiveType.TriangleList, 0, 2 );

device.Transform.World = Matrix.Translation( 1.5f, –1.0f, 0.0f );
device.SetStreamSource( 0, m_vb4, 0, PositionColored.StrideSize );
device.Indices = m_ib4;
device.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, 4, 0, 2 );

// Clip…

device.EndScene();
device.Present();
}

To render the buffers, we first set the Device.VertexFormat property so DirectX will know what type of vertices we will be rendering. When we render the square without the index buffer, we move it a little bit up and to the left by adjusting the world transform of the device. When using vertex buffers, we must first set the stream source with Device.SetStreamSource. This is kind of like loading our rendering “gun” with our vertex “ammo.” We need to load the buffer we want to render into the stream. And then to render the vertex buffer, call Device.DrawPrimitives.

When we render the vertex buffer that uses the index buffer, we first move down and to the right so we don’t render on top of the other square. Then, like before, we set the stream source to the vertex buffer we want to render. Since we will be using an index buffer, we need to tell DirectX what index buffer to use. This is like setting the stream source for vertex buffers. To use the index buffer, we just set the Indices property of the device to point to our index buffer. And finally to render the vertex/index buffer combo, we call Device.DrawIndexedPrimitives.

By using an index buffer, we were able to save a whole 2 vertices from our vertex buffer. Of course, 2 vertices doesn’t really matter, but when a mesh is full of thousands of vertices, a lot of memory can be saved by using an index buffer.