Now that you know how to draw
an object using a vertex buffer, it’s time to take it a step further.
You’ll notice as you advance to more complicated objects you’ll end up
using some vertices more than once, sharing them between triangles. For
instance, drawing a square consisting of two triangles equates to six
vertices. Two of these vertices are used twice, so there’s not really a
reason that they need to be stored in the vertex buffer more than once.
Removing the extra shared vertices and redrawing the object though would
give less than desired results. Index buffers give you the power to
refer to any vertex in the vertex buffer and use it over and over.
The four unique vertices that make up the square
can be stored in the vertex buffer and when the two triangles are
created they both just reference the common vertices. While the savings
on a small object like a simple square won’t be very much, imagine if
you’re eliminating duplicate vertices from a complicated object
containing thousands of triangles.
There are four vertices in the following array declaration. Each of these vertices represents a corner of a square.
// Create vertex buffer
VertexPosStruct vertices[] =
{
D3DXVECTOR3(-0.5f, 0.5f, 0.5f), // 0
D3DXVECTOR3(0.5f, 0.5f, 0.5f), // 1
D3DXVECTOR3(0.5f, -0.5f, 0.5f), // 2
D3DXVECTOR3(-0.5f, -0.5f, 0.5f), // 3
};
Since it would take two triangles to draw the
square, there are not enough vertices listed. By introducing an index
buffer though, the square can be drawn properly. The following
declaration sets up the indices needed for the square.
DWORD indices[] =
{
0,1,3,
1,2,3
};
Note
Triangle strips are not always the answer to
solving a problem of too many vertices. Most 3D objects can’t be broken
down in a single triangle strip but can be optimized using indices.
Creating an Index Buffer
Index buffers and vertex buffers are virtually
identical except for the type of data they contain. Being as they are
both seen by Direct3D as buffer resources, index buffers are created in a
similar fashion to vertex buffers using the CreateBuffer function. The biggest change in the creation process comes in the filling out of the D3D10_BUFFER_DESC structure. Since the buffer being created is an index buffer, the value being passed into the BindFlags variable should be D3D10_BIND-INDEX_BUFFER. This lets Direct3D know the type of buffer to create. An example of how to create an index buffer is shown next.
// The indices that will be in the buffer
DWORD indices[] =
{
0,1,3,
1,2,3
};
// Get the number of indices based on the size of the index array
numIndices = sizeof(indices) / sizeof(DWORD);
// The structure describing how the index buffer should be created
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
bd.ByteWidth = sizeof(DWORD) * numIndices;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = indices;
// Create the index buffer
HRESULT hr = pD3DDevice->CreateBuffer(&bd, &InitData, &pIndexBuffer);
if (FAILED(hr))
{
return false;
}
Since
each object you create will now need to store the index buffer object
as well as the number of indices in the buffer, two new variables can be
added to the ModelObject structure. The new variables are in bold.
typedef struct
{
ID3D10Effect* pEffect;
ID3D10EffectTechnique* pTechnique;
// Vertex information
ID3D10Buffer* pVertexBuffer;
UINT numVertices;
ID3D10InputLayout* pVertexLayout;
// Index information
ID3D10Buffer* pIndexBuffer;
UINT numIndices;
}ModelObject;
DrawIndexed
The Draw function you used previously has a limitation, it can’t draw indexed objects. The DrawIndexed function is used to draw objects using an index buffer. DrawIndexed is very similar to the Draw function discussed earlier except it draws based on indices. The DrawIndexed function takes three parameters.
The first parameter is the number of indices to
use. This will normally be a value equal to the number of indices in
your index buffer.
The second parameter is the starting index
offset. You don’t have to use all the indices in the index buffer, so
this value allows you to begin anywhere in the buffer.
The final parameter is the starting index for the
vertices. This value is the same as the vertex index parameter that you
pass to the Draw function.
An example DrawIndexed call is shown here:
pD3DDevice->DrawIndexed(numIndices, 0, 0);
Before DrawIndexed can correctly draw
your objects, you need to make sure that your vertex layout and vertex
and index buffers have been set correctly. The following code shows the
calls that should be made right before you attempt to draw your object.
// Set the input layout
pD3DDevice->IASetInputLayout(pVertexLayout);
// Set vertex buffer
UINT stride = sizeof(VertexPosStruct);
UINT offset = 0;
pD3DDevice->IASetVertexBuffers(0, 1, &pVertexBuffer, &stride, &offset);
// Set index buffer
pD3DDevice->IASetIndexBuffer(pIndexBuffer, DXGI_FORMAT_R32_UINT, 0 );
// Set primitive topology
pD3DDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
The IASetIndexBuffer function is similar in functionality to IASetVertexBuffers, except the indices are being applied to the hardware.
Although index buffers can definitely benefit you
in terms of optimizing your objects, they have one major downside.
Because index buffers promote vertex sharing, problems can arise
concerning some vertex properties such as colors or texture coordinates.
In the case of a cube, a single vertex will affect three different
sides. If that single vertex contained blue color data, all three sides
will be blending blue. If you then wanted to make each side of the cube a
completely separate color, you would need to duplicate the vertices
that make up those sides and set them with new vertex color data.
A full example of how to use index buffers can be found in the Chapter6\example2 directory on the CD-ROM.
Figure 1 shows a square created by drawing two triangles using the DrawIndexed function.