You’ve probably realized that
most examples start out with drawing a single triangle and you might
wonder why. Like most programming languages, even Direct3D has its own
“Hello World” example in the form of a triangle. It’s the most simple
and straightforward way to describe all the steps needed for 3D drawing
without introducing a bunch of extra code; things that at this point
would only help to confuse and take away from the lesson.
After you’ve endured the
triangle, you’ll build on this simple example, adding complexity but
always understanding why and how it all comes together.
Vertices
The most basic element making up any 3D object is
the vertex. Vertices are the building blocks from which all other
primitives are constructed. Even a triangle relies on three vertices to
make up its simple shape. The vertices dictate the exact location in
space where the sides of the triangle come together.
Vertices, at their most simple form contain at
least a position coordinate. This coordinate contains the same number of
components as the space has axes. For a 2D coordinate system, a vertex
will define its position by using only X and Y component values. When
moving into a 3D space, a third component, Z, is needed.
Vertices are commonly defined in Direct3D using either the D3DXVECTOR2 or D3DXVECTOR3
structure types. Both of these types contain multiple float values,
each one referring to a separate axis. Below you can see an example of
how the D3DXVECTOR3 structure is defined. As you can see, there are three components allowing for positioning within 3D space.
typedef struct D3DXVECTOR3 {
FLOAT x;
FLOAT y;
FLOAT z;
} D3DXVECTOR3;
An individual D3DXVECTOR3 structure can
represent a single vertex; because triangles have three sides, three of
these structures must be filled out. Direct3D also has a D3DXVECTOR4 type, which is useful when storing vertex colors.
Custom Vertex Structure
As you progress into the world of 3D, you’ll find
that all the objects you create will be made up of different types of
vertices. Vertices can have any number of properties associated with
them, from position, color, texture coordinates, and normals. Because of
the dynamic nature of vertices, Direct3D requires you to define the
properties of the vertices you’ll be using. Vertices are defined using a
structure, allowing you
to group all the properties needed together. An example vertex
structure containing only the property for position is shown here:
struct VertexPosStruct
{
D3DXVECTOR3 Pos;
};
The vertex structure is then used to declare an
array of vertices, containing all the vertices that are needed to make
up an object. To demonstrate, a triangle can be defined using the
vertices in the array below.
// Define the vertices for a triangle
VertexPosStruct vertices[] =
{
D3DXVECTOR3(0.0f, 0.5f, 0.5f),
D3DXVECTOR3(0.5f, -0.5f, 0.5f),
D3DXVECTOR3(-0.5f, -0.5f, 0.5f),
};
Most of the objects you create will be much more
complicated than a simple triangle, but even this one triangle requires
the same set up and rendering path as a complicated 3D monster.
Vertex Input Layout
Now that you’ve defined what your vertex
structure will look like, it’s time to put it into a format that
Direct3D can understand: the input layout.
The input layout defines how the vertices will be
fed through the pipeline. As vertices are passed through the pipeline,
Direct3D needs to know how to access the properties contained within
each vertex. The input layout defines not only the order of the vertex
properties, but also their size and type.
When defining an input layout, each property of a vertex can be described using a D3D10_INPUT_ELEMENT_DESC structure.
typedef struct D3D10_INPUT_ELEMENT_DESC {
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D10_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D10_INPUT_ELEMENT_DESC;
Each vertex property needs one of these input
element structures filled out. The structures are then included in an
array and used by Direct3D to create the input layout.
The following code shows an example of how to define a layout for a vertex with both a position and a color.
// The vertex input layout
D3D10_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D10_INPUT_PER_VERTEX_ DATA, 0 },
};
// Calculate the number of elements in the layout array
UINT numElements = ( sizeof(layout) / sizeof(layout[0]) );
A few of the members of the D3D10_INPUT_ELEMENT_DESC structure are detailed next.
SemanticName is the name that the
property will be referenced by within a shader. Most properties in a
vertex have a default semantic name that can be used.
The member Format describes the size of the data this property represents. The position property uses the DXGI_FORMAT_R32G32B32_FLOAT value. This gives each position a 32-bit long float variable to use as storage.
AlignedByteOffset is the number of bytes
that this property is offset from the start of the layout. For
instance, position is first in the input layout so it has an AlignedByteOffset
of 0. Color, which follows the position, has an offset of 12. This says
that position requires 12 bytes at the front of the input layout. As it
may be a bit confusing as to where the value of 12 came from, each
member of the vertex position requires 32 bits or 4 bytes to store their
value. Since there are three values needed to store a vertex position,
it would take 12 bytes to hold this information.
Now that you’ve described the vertex properties,
you can create the input layout object. The input layout object allows
the input stage of the pipeline to know beforehand the format of the data it is ingesting. For more details on the input stage of the pipeline. The input layout object is based on the ID3D10InputLayout interface and is created using the CreateInputLayout function. The following code shows how this function is called.
// Get the pass description
D3D10_PASS_DESC PassDescription;
pTechnique->GetPassByIndex(0)->GetDesc(&PassDescription);
// Create the vertex input layout
hr = pD3DDevice->CreateInputLayout(layout,
numElements,
PassDescription.pIAInputSignature,
PassDescription.IAInputSignatureSize,
&modelObject->pVertexLayout);
if(FAILED(hr))
{
return false;
}
You’ll notice that a call was made to fill in a D3D10_PASS_DESC object. D3D10_PASS_DESC
is used to gather information about the shader that will be used when
drawing this object, specifically the input signature. This makes sure
that the vertex format used to define the object is the same format the
shader expects later when drawing the object.
Note
Each object within a scene may be based on a
different layout of vertex properties. Some may need only position,
while others may require position and color. Not all objects are the
same, so you’ll need to define a vertex input layout for objects using
unique vertex properties.
Right before an object is drawn, the input layout
for the object needs to be set. Since multiple items with different
layouts can be drawn sequentially, Direct3D needs a way of knowing the
layout of the next item it is about to draw. The IASetInputLayout function is used to do this. It takes the input layout object you created and makes it the active layout.
// Set the input layout
pD3DDevice->IASetInputLayout( pVertexLayout );