Áèáëèîòåêà

Beginning DirectX9
Chapter 4 - 3D Primer

Wendy Jones

   Èñòî÷íèê: Beginning DirectX9 Wendy Jones 2004 by Premier Press

You’ve probably noticed that 2D games have been on the decline for the past few years. Recently, games have been all about pushing the power of the latest 3D video cards, trying to bring more reality to the games being played. Direct3D is a key component in the 3D wave. It allows millions of Microsoft Windows users to experience the latest technologies in games.

Here’s what you’ll learn in this chapter:

  • How 3D space is used
  • What coordinate systems are
  • How to plot the points of a polygon
  • How Direct3D defines vertices
  • What a vertex buffer is
  • How to define a 3D scene
  • The different primitive types available to you

3D Space

Previously, I talked about games that only allowed movement in two directions, which created a rather flat world. The sprites you created before resided in a world with width and height but no depth; the sprites existed in a two-dimensional world. Direct3D gives you the power to take your game world one dimension further with the addition of depth. Depth is the ability for objects to move farther away or closer to the viewer. Characters within a three-dimensional world are more realistic than their 2D counterparts.

3D space is the area in which your three-dimensional world exists. 3D space enables your characters to move around in a way that is similar to the real world. Before you can take advantage of 3D space, you need to know how that space is constructed and how objects are placed into it.

Coordinate Systems

Coordinate systems are a way of defining points within a space. They consist of a set of imaginary grid-like lines—called axes—that run perpendicular to one another. A 2D coordinate system contains only two axes, whereas a 3D system adds one more. The center of a coordinate system, where the axes intersect, is called the origin. Figure 4.1 shows what a standard 2D coordinate system looks like. The two axes in a 2D coordinate system are referred to by the letters X and Y. The X axis is horizontal, whereas the Y axis is vertical. You’ll notice in Figure 4.1 that the center X and Y lines are darker than the rest. These lines define the center for each axis and have a value of 0.

Defining a Point in 2D Space

A point is defined as a single position along an axis. A point in 1D space, which consists of a single line, would be referred to by a single value. Figure 4.2 shows a single point plotted on a line. The origin of the line is represented by the value of 0. Points to the right of the origin have a positive value, whereas those to the left of the origin have a negative value. In Figure 4.2, the point has a value of positive 4.


Figure 4.1 How a 2D coordinate system is laid out. Figure 4.2 A 1D coordinate system.

2D coordinate systems, because they have two axes, require a second value to plot a point. To plot a point within 2D space, you need to define a position along both the X and Y axes. For instance, a single point in a 2D coordinate system would be referred to by two numbers—an X value and a Y value—each number referring to the point’s position on that axis.

Like the 1D example shown in Figure 4.2, the values on the X axis continue to increase to the right of the origin, but the values on the Y axis increase as you go up from the origin. Figure 4.3 shows a 2D coordinate system with a point plotted at an X value of 3 and a Y value of 5. You’ll commonly see points shown as (X, Y). In this instance, the point would be shown as (3, 5).

Defining a Point in 3D Space

As I mentioned earlier, a 3D coordinate system adds an extra axis, called the Z axis. The Z axis is perpendicular to the plane created by the X and Y axes. Figure 4.4 shows how a 3D coordinate system would look.

Notice that the coordinate system has the Z axis pointing off into the distance. Commonly, the X and Y axes describe width and height, and the Z axis describes depth.

The Z axis can have either a positive or negative value as it moves away from the origin based on the layout of the coordinate system. Coordinate systems are commonly laid out in either a left-handed or right-handed manner.


Figure 4.3 A point plotted on a 2D coordinate Figure 4.4 A 3D coordinate system layout.

Left-Handed Systems

A left-handed coordinate system extends the positive X axis to the right and the positive Y axis upward. The major difference is the Z axis. The Z axis in a left-handed system is positive in the direction away from the viewer, with the negative portion extending toward him. Figure 4.5 shows how a left-handed coordinate system is set up. This is the coordinate system used in Direct3D.

Right-Handed Systems

The right-handed coordinate system, which is the system used by OpenGL, extends the X and Y axes in the same direction as the left-handed system, but it reverses the Z axis. The positive Z values extend toward the viewer, whereas the negative values continue away. Figure 4.6 shows a right-handed system.

Vertices Explained

A vertex is similar to the idea of the point that I described earlier. A vertex includes information such as location along the X, Y, and Z axes, but it can include other information as well, such as color or texture.


Figure 4.5 A left-handed coordinate system. Figure 4.6 A right-handed coordinate system.

When describing a vertex in code, you can use a structure similar to this:

   struct {
      float x;
      float y;
      float z;
   } vertex;

This vertex structure contains three variables— each one of type float—that describe the vertex’s location along each axis.

Creating a Shape

You can create shapes by using two or more vertices. For instance, in the creation of a triangle, three vertices would be needed to define the three points of the triangle. Plotting a shape with vertices is similar to playing connect the dots. Figure 4.7 shows how to create a triangle by using three vertices.


Figure 4.7 Three vertices create a triangle.

Defining this triangle in code requires three vertex structures.

   struct {
      float x; // the X coordinate
      float y; // the Y coordinate
      float z; // the Z coordinate
   } vertex [ 3 ];

Here, I’ve changed the vertex structure to define an array of three vertices. The next step is setting each of the vertices to the positions found in Figure 4.7.

// Set the first vertex
vertex[0].x = 2.0;
vertex[0].y = 4.0;
vertex[0].z = 0.0;
// Set the X coordinate
// Set the Y coordinate
// Set the Z coordinate
// Set the second vertex
vertex[1].x = 5.0;
vertex[1].y = 1.0;
vertex[1].z = 0.0;
// Set the X coordinate
// Set the Y coordinate
// Set the Z coordinate
// Set the final vertex
vertex[0].x = 2.0;
vertex[0].y = 1.0;
vertex[0].z = 0.0;
// Set the X coordinate
// Set the Y coordinate
// Set the Z coordinate

Notice that the Z coordinate of all three vertices was set to 0.0. This triangle has no depth, so the Z coordinate remains at 0.

n o t e
Triangles are the simplest closed shapes that you can plot with vertices. You can create more complex shapes, such as squares or spheres, but they are broken down into triangles before rendering.

Adding Color

Previously, the vertex structure only included information that related to the position of each vertex. However, vertices can also contain color information. This color information can be held in four additional variables called R, G, B and A.

  • R. The red component of the color
  • B. The green color component
  • B. The blue color component
  • A. The alpha color component

Each of these values helps to define the final color of the vertex. You can see the updated vertex structure next.

   struct {
       // position information
       float x;
       float y;
       float z;
       // color information
       float R;
       float G;
       float B;
       float A;
   } vertex;

Using the R, G, B, and A variables, you can set the color of the vertex. For instance, if you want your vertex to be white in color, the R, G, and B variables should be set to 1.0. Setting the vertex to a pure blue color requires R and G to be set to 0.0, whereas the B variable is set to 1.0.

n o t e
The alpha component of a color determines its transparency level. If the value of an alpha component is 0, the color specified in the R, G, and B values is completely opaque. If the alpha value is greater than 0, the color specified has a level of transparency. The alpha component can be any value between 0.0f and 1.0f.

Vertex Buffers

Vertex buffers are areas of memory that hold the vertex information needed to create 3D objects. The vertices contained within the buffer can contain different kinds of information, such as position information, texture coordinates, and vertex colors. Vertex buffers are useful for storing static geometry that needs to be rendered repeatedly. Vertex buffers can exist in either system memory or the memory on the graphics adapter. A vertex buffer is created by first declaring a variable of type IDirect3DVertexBuffer9. The resulting pointer refers to the vertex buffer that DirectX will create for you. Next, your game must create the vertex buffer and store it in the variable you created. After you have a valid vertex buffer available, you need to fill it with vertices. You can do this by locking the vertex buffer and copying the vertices.

Creating a Vertex Buffer

You can create vertex buffers by using a call to CreateVertexBuffer. The CreateVertexBuffer function, defined next, requires six parameters.

  HRESULT CreateVertexBuffer(
     UINT Length, DWORD Usage,
     DWORD FVF, 3DPOOL Pool,
     IDirect3DVertexBuffer9** ppVertexBuffer,
     HANDLE* pHandle
  );
  • Length. Variable containing the length of the vertex buffer in bytes.
  • Usage. Flags that determine the behavior of the vertex buffer. This value should commonly be 0.
  • FVF. The flexible vertex format that the vertex buffer uses.
  • Pool. The memory pool where the vertex buffer resides. This value is an enumerated value of type D3DPOOL.
  • ppVertexBuffer. An address to a pointer of a variable of type IDirect3DVertexBuffer9. This variable holds the newly created buffer.
  • pHandle. A reserved value, this should always be NULL.
The vertices within a vertex buffer can contain flexible vertex information. Basically, this means that the vertices in the buffer can include just position information, or they can include colors or texture coordinates. The type of data contained within the vertices of the buffer is controlled by the Flexible Vertex Format (FVF) flags.

Flexible Vertex Format

The Flexible Vertex Format allows for customization of the information stored in the vertex buffer. By using a set of FVF flags, the buffer can be made to contain any number of vertex properties. Table 4.1 describes the FVF flags in move detail.

Table 4.1 Flexible Vertex Format Flags
Flag Flag Description
D3DFVF_XYZ Vertex format includes the X, Y, and Z coordinate of an untransformed vertex.
D3DFVF_XYZRHW Vertex format includes the X, Y, and Z coordinates, but this time they are already transformed.
D3DFVF_XYZW Vertex format contains transformed and clipped vertex data.
D3DFVF_NORMAL Vertex format contains normal information.
D3DFVF_PSIZE Format includes the point size of the vertex.
D3DFVF_DIFFUSE Diffuse color is part of the vertex buffer.
D3DFVF_SPECULAR Specular information is part of the vertex buffer.
D3DFVF_TEX0 Texture coordinate 0.
D3DFVF_TEX1 Texture coordinate 1.
D3DFVF_TEX2 Texture coordinate 2.
D3DFVF_TEX3 Texture coordinate 3.
D3DFVF_TEX4 Texture coordinate 4.
D3DFVF_TEX5 Texture coordinate 5.
D3DFVF_TEX6 Texture coordinate 6.
D3DFVF_TEX7 Texture coordinate 7.
D3DFVF_TEX8 Texture coordinate 8.

Direct3D can handle up to eight sets of texture coordinates per vertex. The format of the vertices you use is created by defining a custom vertex structure. The following structure defines a vertex that contains untransformed position information, as well as a color component.

	struct CUSTOMVERTEX
		{
		FLOAT x, y, z, rhw; // the untransformed, 3D position for the vertex
		DWORD color; // the vertex color
	};

The CUSTOMVERTEX structure consists of the standard vertex position variables X, Y, and Z, but also includes the variable RHW. The RHW value, which stands for Reciprocal of Homogeneous W, tells Direct3D that the vertices that are being used are already in screen coordinates. This value is normally used in fog and clipping calculations and should be set to 1.0.

n o t e
The vertex color is a DWORD value. Direct3D provides a few macros to assist you in creating these colors. One such macro is the D3DCOLOR_ARGB(a, r, g, b) macro. This macro accepts four components: an alpha, a red, a green, and a blue. Each of these components is a value between 0 and 255. This macro returns a DWORD color value that Direct3D can use. D3DCOLOR_ARGB(0, 255, 0, 0) creates a color value representing all red. Additional macros are D3DCOLOR_RGBA and D3DCOLOR_XRGB, which are described fully in the DirectX documentation.

Now that the vertex structure is created, the next step is determining the flags that will be sent to the CreateVertexBuffer function as the FVF parameter.

Because the CUSTOMVERTEX structure requires non-transformed position information and a color component, the needed flags would be D3DFVF_XYZRHW and D3DFVF_DIFFUSE.

The sample code that follows shows a call to CreateVertexBuffer using this vertex structure.

	// a structure for your custom vertex type
	struct CUSTOMVERTEX
	{
		FLOAT x, y, z, rhw; // the untransformed, 3D position for the vertex
		DWORD color; // the vertex color
	};
	// Create the variable to hold the vertex buffer
	LPDIRECT3DVERTEXBUFFER9 buffer = NULL;
	// variable used to hold the return code
	HRESULT hr;
	// Create the vertex buffer
	hr = pd3dDevice->CreateVertexBuffer(
	3*sizeof( CUSTOMVERTEX ),
	0,
	D3DFVF_XYZRHW | D3DFVF_DIFFUSE,
	D3DPOOL_DEFAULT,
	&buffer,
	NULL );
	// Check the return code
	if FAILED ( hr)
	return false;

As you can see, the CUSTOMVERTEX structure is created first, telling Direct3D the type of vertices to use. Next, the call to CreateVertexBuffer creates the actual buffer and stores it in the buffer variable.

The first parameter to CreateVertexBuffer, the size of the buffer in bytes, is created with enough space to hold three vertices of type CUSTOMVERTEX. The third parameter, the FVF, is shown as having the flags D3DFVF_XYZRHW and D3DFVF_DIFFUSE being used.

The fourth parameter sets the memory pool for this vertex buffer. The value D3DPOOL_DEFAULT is used, which allows the buffer to be created in the most appropriate memory for this type.

The final parameter that you have to worry about is the fifth one. This is where you pass in the variable that holds the newly created buffer.

After the call to CreateVertexBuffer is complete, make sure to check the return code to confirm that the buffer was created successfully.

Loading Data into a Buffer

Now that you have a valid vertex buffer, you need to add vertices to it. Before you can place vertices in the buffer, you must lock the memory the buffer is using. After this memory is locked, it is freely available to be written to by your game.

Locking the Vertex Buffer

Locking the memory used by the vertex buffer allows your application to write to it. At this point, you’ve already defined the vertex buffer and the type of vertices it will hold. The next step is locking the buffer and copying the vertices. Locking of the buffer is accomplished with the Lock function, defined next.

	HRESULT Lock(
		UINT OffsetToLock,
		UINT SizeToLock,
		VOID **ppbData,
		DWORD Flags
	);

The Lock function takes four parameters:

  • OffsetToLock. The offset into the buffer to lock. If you want to lock the entire buffer, this value should be 0.
  • SizeToLock. The size in bytes to lock. Again, if you are locking the whole buffer, this value should be 0.
  • ppbData. A void pointer to the buffer that holds the vertices.
  • Flags. Flags that describe the type of lock. Following are the available flags:
    • D3DLOCK_DISCARD. The entire buffer is overwritten.
    • D3DLOCK_NO_DIRTY_UPDATE. Dirty regions of the buffer are not written to.
    • D3DLOCK_NO_SYSLOCK. The system keeps normal display mode changes from happening during a lock. This flag enables the system to continue processing other events.
    • D3DLOCK_READONLY. The buffer cannot be written to.
    • D3DLOCK_NOOVERWRITE. Any information currently in the buffer is not to be overwritten.

The following code sample shows a normal call to the Lock function.

	HRESULT hr;
	VOID* pVertices;
	// Lock the vertex buffer
	hr = g_pVB->Lock( 0, 0, ( void** ) &pVertices, 0 );
	// Check the return code to make sure the lock was successful
	if FAILED (hr)
	return false;

The Lock function assumes that you’ve already created a valid vertex buffer. The variable g_pVB refers to this buffer.

Copying Vertices to a Vertex Buffer

After the vertex buffer is locked, you can freely copy data into the buffer. You can either copy enough vertices to fill the whole buffer, or you can selectively change vertices within the buffer. The next example shows how to use memcpy to copy an array of vertices into a vertex buffer.

	// the customvertex structure
	struct CUSTOMVERTEX
	{
		FLOAT x, y, z, rhw; // the transformed, 3D position for the vertex
		DWORD color; // the vertex color
	};
	// Define the vertices to be used in the buffer
	CUSTOMVERTEX g_Vertices [ ] =
	{
		{320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_ARGB (0, 255, 0, 0),},
		{250.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_ARGB (0, 0, 255, 0),},
		{50.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_ARGB (0, 0, 0, 255),},
	};
	// Copy the vertices into the vertex buffer
	memcpy( pVertices, g_Vertices, sizeof( g_Vertices ) );

The sample first declares the CUSTOMVERTEX structure. As mentioned before, this structure takes a position vertex as well as a color component. Next, an array of vertices is created. The array, referred to by the g_Vertices variable, holds the vertices to be copied into the buffer. Finally, a call to memcpy is made to copy the vertices into the buffer. The first parameter to memcpy, pVertices, refers to the void pointer that was created during the call to Lock.

Unlocking the Vertex Buffer

After the vertices have been copied into the buffer, you must unlock the buffer. Unlocking the buffer allows Direct3D to continue processing normally. You can unlock the buffer through the Unlock function, defined here:

	HRESULT Unlock (VOID);

The Unlock function requires no parameters and returns the value of D3D_OK on success. After the vertex buffer is filled with vertices, it’s ready to be drawn to the screen. The SetupVB function that follows takes all the steps from earlier and places them in an easy-to-use function.

	// variable to hold the newly created vertex buffer
	LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
	/******************************************************************************
	* SetupVB
	* Creates and fills the vertex buffer
	******************************************************************************/
	HRESULT SetupVB()
	{
		HRESULT hr;
		// Initialize three vertices for rendering a triangle
		CUSTOMVERTEX g_Vertices[] =
		{
			{320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_ARGB (0, 255, 0, 0), },
			{250.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_ARGB (0, 0, 255, 0), },
			{50.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_ARGB (0, 0, 0, 255), },
		};
		// Create the vertex buffer
		hr = pd3dDevice->CreateVertexBuffer(
		3*sizeof(CUSTOMVERTEX),
		0,
		D3DFVF_XYZRHW|D3DFVF_DIFFUSE,
		D3DPOOL_DEFAULT,
		&g_pVB,
		NULL );
		// Check to make sure that the vertex buffer was
		// created successfully
		if FAILED ( hr )
			return NULL;
		VOID* pVertices;
		// Lock the vertex buffer
		hr = g_pVB->Lock( 0, sizeof(g_Vertices), (void**)&pVertices, 0 );
		// Check to make sure the lock was successful
		if FAILED (hr)
		return E_FAIL;
		// Copy the vertices into the buffer
		memcpy( pVertices, g_Vertices, sizeof(g_Vertices) );
		// Unlock the vertex buffer
		g_pVB->Unlock();
		return S_OK;
	}

The SetupVB function requires that a variable to hold the vertex buffer is defined outside the scope of this function. The variable g_pVB refers to this variable. If the vertex buffer is created and filled successfully, the SetupVB function returns the HRESULT value of S_OK.

Drawing the Contents of the Buffer

Now that you’ve spent all this time creating the vertex buffer and filling it with vertices, you’re probably wondering when you get to see something on the screen. Well, rendering the vertices within the vertex buffer requires three steps. The first step is setting the stream source, followed by configuring the vertex shader, and then finally drawing the vertices to the screen. These steps are explained in detail in the following sections.

Setting the Stream Source

Direct3D streams are arrays of component data that consist of multiple elements. The vertex buffer you created earlier is an example of such a stream. Before Direct3D can render a vertex buffer, you must associate the buffer with a data stream. This is accomplished with the function SetStreamSource, defined here:

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

SetStreamSource requires four parameters.

  • StreamNumber. The number of the data stream. If you have created only one vertex buffer, this parameter is 0.
  • pStreamData. The pointer to the variable that contains the vertex buffer.
  • OffsetInBytes. The number of bytes from the start of the buffer where the vertex data is stored. This value is usually 0.
  • Stride. The size of each vertex structure within the buffer.

An example call to SetStreamSource is shown next:

	pd3dDevice->SetStreamSource ( 0, buffer, 0, sizeof(CUSTOMVERTEX) );

In this function call to SetStreamSource, the first parameter representing the stream number is set to 0. The second parameter must be a valid pointer to a properly created vertex buffer. The third parameter is set to 0, telling Direct3D to start at the beginning of the stream. The final parameter is the stride of the stream. This is set to the size in bytes of the CUSTOMVERTEX structure. The sizeof function calculates the number of bytes.

Setting the Vertex Shader

After you set the source for the stream, you must set the vertex shader. The vertex shader tells Direct3D which types of shading to apply. The SetFVF function, defined next, sets up Direct3D to use a fixed vertex function format.

	HRESULT SetFVF(
		DWORD FVF
	);

The SetFVF function requires only one parameter specified by the variable FVF. The FVF parameter accepts a value of type D3DFVF.

The following code sample shows how SetFVF is used.

	HRESULT hr;
	hr = pd3dDevice->SetFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
	// Check the return code to verify that SetFVF completed successfully
	if FAILED (hr)
		return false;

This code sample passes the values D3DFVF_XYZRHW and D3DFVF_DIFFUSE as the parameter to SetFVF. As you’ll recall, when the CUSTOMVERTEX structure was set up, it used these two values when creating the vertex buffer.

You must have already created a valid Direct3D device. It is referred to by the pd3dDevice variable.

Rendering the Vertex Buffer

Now that you have created the stream and associated it with the vertex buffer, you can render the vertices to the screen. The function needed to do this is DrawPrimitive, defined next. The DrawPrimitive function continues through the vertex buffer and renders its data to the screen.

	HRESULT DrawPrimitive(
		D3DPRIMITIVETYPE PrimitiveType,
		UINT StartVertex,
		UINT PrimitiveCount
	);

The DrawPrimitive function requires three parameters:

  • PrimitiveType. The type of primitive to draw using the vertices within the stream
  • StartVertex. The number of the first vertex in the stream
  • PrimitiveCount. The number of primitives to render The PrimitiveType parameter can be any of these enumerated values:
  • D3DPT_POINTLIST. A series of individual, unconnected points
  • D3DPT_LINELIST. Isolated lines
  • D3DPT_LINESTRIP. A series of lines connected by a single vertex
  • D3DPT_TRIANGLELIST. Isolated triangles consisting of three vertices
  • D3DPT_TRIANGLESTRIP. A series of connected triangles where only one vertex is required for the definition of each additional triangle
  • D3DPT_TRIANGLEFAN. A series of connected triangles that share a common vertex

The following code segment shows a call to DrawPrimitive using a triangle strip as the primitive type.

	HRESULT hr;
	// Call DrawPrimitive
	hr = pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 1 );
	// Check the return code to verify that the function was successful
	if FAILED (hr)
		return false;

The previous code sample tells Direct3D to render the vertices in the vertex buffer using a triangle strip described using the D3DPT_TRIANGLESTRIP type as the first parameter. The second parameter is set to 0, meaning that DrawPrimitive should start with the first vertex in the buffer. The last parameter is set to 1 because there were only enough vertices defined to create a single triangle.

A valid Direct3D device must exist. It is referred to by the pd3dDevice variable.

The full source for creating and rendering a vertex buffer is available in the chapter4\example1 directory on the CD-ROM.

Figure 4.8 shows the drawing of a single colored triangle.


The output of Example 1.

Rendering a Scene

Before you can render 3D primitives, you must prepare Direct3D to render. The BeginScene function tells Direct3D that rendering is about to take place. Using the BeginScene function, Direct3D makes sure that the rendering surfaces are valid and ready. If the BeginScene function fails, your code should skip making rendering calls.

After rendering is done, you need to call the EndScene function. The EndScene function tells Direct3D that you are finished making rendering calls and the scene is ready to be presented to the back buffer.

The code that follows confirms the return codes from BeginScene and EndScene.

	HRESULT hr;
	if ( SUCCEEDED( pDevice->BeginScene( ) ) )
	{
		// Render primitives only if the scene
		// starts successfully
		// Close the scene
		hr = pDevice->EndScene( );
		if ( FAILED ( hr ) )
		return hr;
	}

The previous code confirms that the call to BeginScene is successful before allowing rendering to take place using the SUCCEEDED macro around the call. When rendering is complete, you call the EndScene function.

The next code sample shows what an example render function might look like.

	/******************************************************************************
	* render
	******************************************************************************/
	void render()
	{
		// Clear the back buffer to black
		pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET,
		D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
		// Tell Direct3D to begin the scene
		pd3dDevice->BeginScene();
		// Draw the contents of the vertex buffer
		// Set the data stream first
		pd3dDevice->SetStreamSource( 0, buffer, 0, sizeof(CUSTOMVERTEX) );
		// Set the Vertex format for the stream next
		pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
		// Draw the vertices within the buffer using triangle strips
		pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 1 );
		// Tell Direct3D that drawing is complete
		pd3dDevice->EndScene();
		// copies the back buffer to the screen
		pd3dDevice->Present( NULL, NULL, NULL, NULL );
	}

The render function takes all these steps and combines then into a single function. The pd3dDevice variable represents a valid Direct3D device created outside this function.

Primitive Types

Earlier, you had the option of setting the primitive type that DrawPrimitive would use to render the vertices within the vertex buffer. For the purpose of the previous example, I chose a triangle strip for its speed and ability to add additional triangles easily. This section explains in a little more detail the differences among the available primitive types.

Point List

A point list consists of a series of points that are not connected in any way. Figure 4.9 shows a grid containing four distinct points. Each point is defined using X, Y, and Z coordinates. For example, the top-left point would be defined as (1, 6, 0).

Line List

A line list consists of lines constructed by two points, one at each end. The lines within a line list are not connected. Figure 4.10 shows two lines rendered using a line list. This particular line list is constructed from four vertices. The line on the left is formed using (-6, 5, 0) for the upper coordinate and (-4, 2, 0) for the bottom coordinate.

Line Strip

A line strip is a series of connected lines in which each additional line is defined by a single vertex. Each vertex in the line strip is connected to the previous vertex for a line. Figure 4.11 shows how a line list is constructed and rendered. The line list in this figure is constructed using a series of six vertices, creating five lines.

Triangle List

A triangle list contains triangles that are not connected in any way and can appear anywhere within your world. Figure 4.12 shows two individual triangles constructed from six vertices. Each triangle requires three vertices to construct a complete triangle.


Figure 4.9 An example of rendered points using a point list. Figure 4.10 Lines rendered using a line list.


Figure 4.11 Lines rendered using a line strip. Figure 4.12 Triangles rendered using atriangle list.

Triangle Strip

A triangle strip is a series of triangles connected to one another in which only one vertex is required to define each additional triangle. Figure 4.13 shows four triangles created using only six vertices.

Triangle strips are constructed first by creating three vertices to define the first triangle. If an additional vertex is defined, lines are drawn between the two previously created vertices, forming another triangle. In Figure 4.13, the order of the vertices’ creation is shown.


Figure 4.13 Triangles rendered using a triangle strip.

Triangle Fan

A triangle fan is a series of triangles that share a common vertex. After the first triangle is created, each additional vertex creates another triangle with one of its points being the first vertex defined.

Figure 4.14 shows how a triangle fan consisting of three triangles is created using only five vertices. The order of the vertices controls what the triangle fan looks like. Figure 4.14 shows the order of the vertices’ creation needed to construct the displayed fan.


Figure 4.14 Triangles rendered using a triangle fan.