Chapter 5: 3D Primitives and Vertex Buffers

 

When we want Direct3D to render a 3D object for us, we start off by defining that object as a collection of vertices. We then give the object a more solid feel by connecting the vertices together with line segments. The vertices define the model in 3D space and the line segments define the object’s outer skin or surface by joining the vertices together in groups.

 


Figure 5-1: The famous “teapot”, which you’ll get to know so well as a 3D graphics programmer

 

At first, this seems simple enough but many beginners become confused once they realize that 3D objects are defined completely by their vertices, We never actually define the object’s surface by saying, “connect this vertex to that vertex with a line”. So, where do these line segments come from if we don’t actually define them? Well, the line segments are simply implied by vertex order and a set of predefined rules for geometry construction which have been laid down by Direct3D. These rules are basically defined through what is called a, “3D primitive”, which is a collection of vertices that form a single 3-D entity. When we define a 3D object we need to pick the 3D primitive we want to use and define our object’s vertices accordingly so Direct3D can interpret them correctly during rendering.

 

Of course, half of these 3D primitives deal specifically with triangles, which I’ve already described as being the basic building block and work-horse of modern 3D graphics. The other 3D primitives deal with points and lines, which can be very useful in creating special tools like model and level editors where an artist or modeler may need to manipulate the actual vertices and edges of model’s geometry.

 

In all, Direct3D defines six 3D primitives:

 

Point Lists

Line Lists

Line Strips

Triangle Lists

Triangle Strips

Triangle Fans

 

Direct3D gives each primitive type an enumerated name, which is defined under the D3DPRIMITIVETYPE data type like so:

 

typedef enum _D3DPRIMITIVETYPE {

    D3DPT_POINTLIST = 1,

    D3DPT_LINELIST = 2,

    D3DPT_LINESTRIP = 3,

    D3DPT_TRIANGLELIST = 4,

    D3DPT_TRIANGLESTRIP = 5,

    D3DPT_TRIANGLEFAN = 6,

    D3DPT_FORCE_DWORD = 0x7fffffff

} D3DPRIMITIVETYPE;

 

Point Lists

 

A point is the simplest of all primitives. If our buffer of vertices is treated as a point list, then each vertex is nothing more than a point in 3D space and no attempt will be made to connect the vertices with edges. Obviously, this primitive is seldom used since it’s difficult to render any thing of interest with it.

 


Figure 5-2: A point list containing six points or vertices

 

Line Lists

 

In a line list, our vertices are used to create isolated and unconnected line segments. For example, if we call the first four vertices in the buffer v0, v1, v2, and v3; then v0 and v1 defines the first line segment, and v2 and v3 define the second line segment.

 


Figure 5-3: A line list containing three line segments, which are defined by six vertices

 

Because line lists pair-up vertices to make line segments, we can not use this primitive with vertex buffers that contain an odd number of vertices.

 

Line Strips

 

A line strip is similar to a line list, but instead of rendering a collection of isolated line segments, which may or may not be touching a line strip renders a series of connected line segments by simply connecting each vertex to the last one with an edge.

 


Figure 5-4: A line strip consiting of five connected line segments, which are defined by six vertices

 

It’s apparent that line strips are more restrictive than a line list since all line segments have to touch end-to-end, but if this is what you want, line strips require fewer vertices than a line list because it eliminates redundant vertices where line segments meet. For example, a line list containing two line segments needs four vertices to define the two segments even when connected, but a line strip only needs three vertices to define the same poly line.

 


Figure 5-5: By using a line strip instead of a line list, vertices can be saved.

 

Saving one vertex, as depicted in the figure above, may not seem like much, but when you’re dealing with an object containing thousands of vertices the savings can be considerable.

 

Triangle Lists

 

A triangle list is a collection of isolated, unconnected triangles, which may or may not be touching each other. You simply define three vertices for each triangle needed. Since this primitive is easy to visualize and understand you’ll see it used quite a bit in samples and demos.

 


Figure 5-6: A triangle list containing two triangles, which are defined by six vertices

 

Triangle Strips

 

Just like line strips, which is a more efficient way of storing line segments, triangle strips are a more efficient way of storing triangles. If we have a long run of connected triangles, it will take fewer vertices to store them as a triangle strip than to store them in a triangle list. In the following figure, it’s easy to see how vertices can be saved as the first triangle is defined by v0, v1, and v2, but the next triangle only requires the addition of a single vertex, v3, to be defined. In other words, once the first triangle has been laid, each additional vertex plotted results in a complete triangle through implied edges which reach back to the last triangle in the strip.

 


Figure 5-7: A triangle strip defined by seven vertices

 

Triangle strips are by far the most popular primitive used today and considerable research has been done into converting regular un-optimized triangle meshes into tri-strips.

 

Triangle Fans

 

Like triangle strips, triangle fans also use shared vertices to eliminate redundancy, but instead of just creating a long strip, the potential for sharing is increased by having all triangles within the fan share a single, common vertex. For example, the triangle fan illustrated below, not only contains triangles which share vertices with their neighbors, but each triangle shares vertex v0 as a common vertex.

 


Figure 5-8: A triangle fan defined by six vertices

 

It’s a little harder to find good places in your models to use fans, but if you can find them, triangle fans are by far the most efficient way of storing 3D geometry.

 

Winding Order of Vertices

 

When defining the triangles that make up the geometry of our 3D models, it’s important to understand that the “winding order” of a triangle’s vertices determine whether we’re viewing the triangle’s front face or its back face. At first, this aspect of a triangle may not seem very important since both faces of a triangle look pretty much the same, but it will become very important when it comes time to render our triangles since, by default, only the front face of a triangle gets rendered.

 

Of course, there is a way to turn off this feature so that both faces of a triangle get rendered but it’s a bad idea to get into this habit, especially if you’re concerned with performance. The reason for this is Direct3D supports a feature called “back-face culling” which allows 3D applications to run faster by culling away triangles which contribute nothing to the final scene.

 


Figure 5-9: The winding order of a triangle lets us know if we’re looking at its front or its back

 

To understand how back-face culling works, imagine for a moment that we created a 3D model of a highly tessellated sphere where we made sure that the winding order of each triangle placed its front face pointing out and away from the sphere’s interior.

 

Now, if we were to render our sphere with out any back-face culling, Direct3D would simply push all the triangles that define it onto the video card for processing with out giving it second thought. Unfortunately, this is partial waste of time since the video card will process every triangle – even the ones that lie on the sphere’s far side and can’t be seen. Why would we want to waste time processing triangles that we can’t even see? They contribute nothing to the scene! Of course, you already know the answer – use back-face culling. With back face culling turned on, Direct3D will cull away any triangle which has its back facing towards us, and since we modeled our sphere correctly, that would be all triangles on the far side.

 

Flexible Vertex Format

 

Up until this point, we’ve been talking about 3D primitives and the vertices they represent under very simplistic terms.  More specifically, we’ve been treating our vertices as nothing more than 3D points in 3D space. This was perfectly fine when discussing 3D primitives because that’s all that really matter at that point (pardon the pun), but to create realistic 3D models we need to add additional information to each vertex. At the very least, we should add a color value per vertex so our geometry can be rendered in other colors besides white, which is the default color in Direct3D. If everything was just white, our scene would hardly look 3D.

 


Figure 5-10: Without color, even 3D objects look flat

 

Of course, we can’t just go around arbitrarily adding things to each vertex with out giving Direct3D a heads-up. If we don’t tell Direct3D that all of our vertices have both a position and a color, how will it know what its reading when we pass the vertex data to it?

 

The answer to this is called a Flexible Vertex Format code. A Flexible Vertex Format (FVF) code describes the contents of stored vertices to Direct3D so it can clearly decipher what’s being passed to it. Typically, this code is created as a simple define using FVF Flags, which have been predefined for your use by Direct3D. In the following code snippet, we use two FVF Flags, D3DFVF_XYZ and D3DFVF_DIFFUSE, to create a constant called D3DFVF_MY_VERTEX. We could have called the constant anything we wanted but its common practice to at least start it off with D3DFVF_ so other programmers know what purpose it serves. After we’ve defined our FVF code we’ll need to create a structure for our vertex type so it matches exactly with what our FVF code defines. If we add or leave something out, or change the order of the variables within the structure, Direct3D will misinterpret our vertex data and our application will either render incorrect geometry or crash completely.

 

#define D3DFVF_MY_VERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE )

 

struct Vertex

{

    float x, y, z; // Position of vertex in 3D space

    DWORD color;   // Color of vertex

};

 

Our vertex structure, which is simply called “Vertex”, allocates space for three floats for its position and a single DWORD for color. The variable names could be whatever you want, but the number of variables, their types and order of placement can not be different from what was defined by the FVF Flags used by D3DFVF_MY_VERTEX.

 

Here’s a few of the more popular FVF flags in order along with their associated data types and required layout:

 

D3DFVF_XYZ      - untransformed vertex = 3 floats

D3DFVF_XYZRHW   - transformed vertex   = 4 floats

D3DFVF_NORMAL   - normal vector        = 3 floats

D3DFVF_DIFFUSE  - diffuse color        = 1 DWORD

D3DFVF_SPECULAR - specular color       = 1 DWORD

D3DFVF_TEX1     - texture coordinates  = 1,2,3, or 4 floats

 

It will no doubt take some time getting used to creating your own vertex structures from scratch so I would suggest looking over the SDK documentation carefully with regards to FVF Flags. As we progress through the book we’ll return to this subject again and again as we add new flags to our FVF code. As an additional example here is FVF code definition and vertex structure suitable for lit geometry:

 

#define D3DFVF_MY_LIT_VERTEX ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE )

 

struct Vertex

{

    float  x,  y,  z; // Position of vertex in 3D space

    float nx, ny, nz; // Normal vector for lighting calculations

    DWORD color;     // Diffuse color of vertex

};

 

With this configuration, each vertex that makes up our geometry consists of a position, a normal for lighting, and a diffuse color.

 

Vertex Buffers

 

Once you’ve come to grips with the 3D primitives and understand how their vertices get handled by Direct3D, you’ll need a way of storing your vertices until it’s time to render them. When working with Direct3D, we store our vertices using a Vertex Buffer, which is defined by the LPDIRECT3DVERTEXBUFFER9 data type. The creation of a Vertex Buffer is accomplished by calling CreateVertexBuffer, which is one of the methods contained in the IDirect3DDevice9 interface.

 

HRESULT CreateVertexBuffer( UINT Length,

    DWORD Usage,

    DWORD FVF,

    D3DPOOL Pool,

    IDirect3DVertexBuffer9** ppVertexBuffer,

    HANDLE* pHandle

);

 

The following code snippet demonstrates how to create a Vertex Buffer suitable for holding three vertices. You can follow along using the “vertex_buffer” sample.

 

LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL;

 

#define D3DFVF_MY_VERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE )

 

struct Vertex

{

    float x, y, z; // Position of vertex in 3D space

    DWORD color;   // Color of vertex

};

 

...

 

if( FAILED( g_pd3dDevice->CreateVertexBuffer( 3*sizeof(Vertex),

                                              D3DUSAGE_WRITEONLY,

                                              D3DFVF_MY_VERTEX,

                                              D3DPOOL_DEFAULT,

                                              &g_pVertexBuffer,

                                              NULL ) ) )

{

    // TO DO: Respond to failure of CreateVertexBuffer

    return;

}

 

For the first argument, Length, we specify how big the buffer needs to be by simply multiply the size of our Vertex structure by three and passing in the result.

 

The second argument, Usage, is where we set any special property flags for our new Vertex Buffer. By passing D3DUSAGE_WRITEONLY, we are telling Direct3D that we intend to write to our buffer - but never read from it. Setting this allows Direct3D to place our new vertex data in the most optimal memory for both writing and rendering. If you’re uncertain about your writing and reading needs, just pass D3DPOOL_DEFAULT instead.

 

The third argument, FVF, is where we pass in our custom FVF flags, which gives Direct3D the necessary heads-up concerning the layout of our vertices as defined by the Vertex structure.

 

The fourth argument, Pool, is how we specify where the memory for our new Vertex Buffer should be allocated from. This is fairly advanced optimization related feature, so for now we’ll leave this decision up to Direct3D by passing D3DPOOL_DEFAULT.

 

The fifth argument, ppVertexBuffer, is where we pass in the pointer to our LPDIRECT3DVERTEXBUFFER9 object. If the call to CreateVertexBuffer succeeds, the pointer passed in will point to a valid Vertex Buffer that we can use.

 

And finally, the sixth argument is reserved for future use and we simply set it to NULL.

 

Loading Vertex Buffers

 

After we’ve successfully created a Vertex Buffer we’ll need to lock it long enough to load our vertex data into to it. This is accomplished by calling LPDIRECT3DVERTEXBUFFER9’s Lock method:

 

HRESULT Lock( UINT OffsetToLock,

    UINT SizeToLock,

    VOID **ppbData,

    DWORD Flags

);

 

Locking is necessary with Vertex Buffers since it’ unsafe to cache a pointer to the data buffer itself. This is because Direct3D is capable of speeding things up by moving the vertex data from system memory to video memory and vice versa if it becomes necessary to free up valuable video memory for more important resources. Locking basically ensures that we can safely access the vertex data without interfering with Diredt3D’s internal memory management. The ability to lock and unlock a vertex buffer is basically what separates a fancy Vertex Buffer from a regular user defined array.

 

Here’s how we call Lock on our Vertex Buffer so we can load it with vertex data:

 

Vertex *pVertices = NULL;

 

if( FAILED( g_pVertexBuffer->Lock( 0, 0, (void**)&pVertices, 0 ) ) )

{

    // TO DO: Respond to the failure of calling Lock on our Vertex Buffer

    return;

}

else

{

    //

    // The call to Lock was successful and pVertices now points to the

    // actual vertex buffer. We can now define our triangle's three

    // vertices.

    //

 

    // Triangle's left vertex

    pVertices[0].x = -1.0f;

    pVertices[0].y =  0.0f;

    pVertices[0].z =  0.0f;

    pVertices[0].color = D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 ); // red

 

    // Triangle's top vertex

    pVertices[1].x = 0.0f;

    pVertices[1].y = 1.0f;

    pVertices[1].z = 0.0f;

    pVertices[1].color = D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 ); // green

 

    // Triangle's right vertex

    pVertices[2].x = 1.0f;

    pVertices[2].y = 0.0f;

    pVertices[2].z = 0.0f;

    pVertices[2].color = D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 ); // blue

}

 

g_pVertexBuffer->Unlock();

 

The code defines a simple multi-colored triangle and it’s important to understand that even though there’s no explicit mention of the D3DPT_TRIANGLELIST primitive type, we did define a triangle suitable for it. The actual declaration of the primitive type won’t be until we’re ready to render using this vertex buffer.

 


Figure 5-11: Here’s our triangle plotted on Direct3D’s coordinate system

 

Rendering With Vertex Buffers

 

Finally, with our vertex buffer created and loaded with valid data, we’re ready to render our triangle. The following piece of code demonstrates how to render the triangle we loaded into our vertex buffer:

 

g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(Vertex) );

g_pd3dDevice->SetFVF( D3DFVF_MY_VERTEX );

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );

 

The first call to, SetStreamSource, basically binds or identifies our vertex buffer as the primary source of vertex data for our Direct3D device to use.

 

HRESULT SetStreamSource( UINT StreamNumber,

    IDirect3DVertexBuffer9 *pStreamData,

    UINT OffsetInBytes,

    UINT Stride

);

 

For the first argument, StreamNumber, we simply pick the primary stream by passing a 0. This book will not be covering any topics that require multiple streams, so we will always pass 0 for this argument.

 

The second argument, pStreamData, is where we pass in the actual vertex buffer. In our case, we pass in g_pVertexBuffer, which is the vertex buffer that contains our triangle.

 

The third argument, OffsetInBytes, gives us the ability to skip ahead in the vertex buffer and begin rendering with a primitive other than the first one. For example, if we had defined two triangles instead of just one for our vertex buffer, we could’ve skipped the rendering of the first one and started with the second one instead. Since we only have a single triangle defined and there’s nothing else to skip to, we’ll simply pass 0. Of course, if you did want to use an offset, you would have to make sure that your hardware supports this feature since not all cards allow offsetting of the data stream.

 

The last argument, Stride, tells Direct3D how many bytes are used by each vertex. The stride is what allows Direct3D to find the individual vertices in the data stream. The easiest way to set this is to simply call sizeof() on our Vertex structure.

 

The second method called is to SetFVF, which helps Direct3D interpret the incoming stream of vertex data by defining the layout of each vertex in the buffer.

 

HRESULT SetFVF(

    DWORD FVF

);

 

The method takes only one argument - the FVF code used by the vertex buffer. In our case, we pass in our custom FVF code which we defined as D3DFVF_MY_VERTEX.

 

Finally, with our vertex buffer identified as the primary data stream and the FVF code set, we can render our triangle by calling the last method, DrawPrimitive.

 

HRESULT DrawPrimitive( D3DPRIMITIVETYPE PrimitiveType,

    UINT StartVertex,

    UINT PrimitiveCount

);

 

We set the first argument, PrimitiveType, to D3DPT_TRIANGLELIST since we loaded our vertex buffer with vertices conforming to this primitive’s layout and usage.

 

The second argument, StartVertex, is similar to the OffsetInBytes argument of the SetStreamSource method since it allows us to skip ahead and start rendering with any vertex we want. The main difference is StartVertex skips past vertices by specifying a buffer index instead of specifying the number of raw bytes to skip. Either way, we only have one triangle and we don’t want to skip it, so we simply set the starting index to 0.

 

The final argument, PrimitiveCount, identifies the total number of primitives that we want to render from our buffer and since we have only one triangle, we’ll set it to 1.

 

We’ll cover more complicated examples of vertex buffers in a just moment, but we need stop long enough to cover an important topic: double-buffering.

 

Rendering & Double-Buffering

 

Now that you know how to render using a Vertex Buffer and DrawPrimitive, it’s important to understand that you can’t simply render when ever you want to. The act of rendering 3D geometry must be done in an orderly fashion or things will get ugly quick. The reason for this is due to the way Direct3D based applications write pixel data to the monitor. It may seem strange to be thinking about pixels when you’re working with 3D geometry but displays and monitors only work with pixel data, so the end result of all applications, including 3D applications, is a buffer full of pixels.

 

Of course, writing pixels to the monitor is only part of the reason why we need to synchronize our applications rendering; the other reason has to do with smooth animation. Like a theatre projector, 3D applications produce animated scenes by showing the viewer a rapidly changing succession of images. The only real difference between a real-time 3D applications and a theater projector is a real-time 3D application can not store the individual images that make up the scene’s motion ahead of time like a projector does with film. It must dynamically create each image just prior to displaying it. If these images move fast enough they seem to blur together and the human eye can be fooled into thinking it’s seeing a continuous stream of motion. If the images move too slowly, the motion becomes jerky and the eye gets wise to the illusion. Needless to say, the faster we can dynamically create and display the individual images, the smoother the motion will be.

 

Unfortunately, monitors have physical limits and we can’t simply go crazy and begin firing off hundreds of pixel buffers at the monitor and just expect it to display them without trouble. The heart of the problem lies with the monitor’s refresh-rate and vertical retrace. The refresh-rate (measured in Hertz), identifies how many times a second the monitor can be updated without producing any graphical anomalies, while the monitor’s vertical retrace identifies the amount of time required after each update before the monitor is ready for the next update. The following figure demonstrates this situation by tracing out the path taken by the monitor’s electron-gun, which is what ultimately produces the final image:

 


Figure 5-12: The electron gun’s path as it lights up the pixels on each scan-line of a monitor.

 

With the start of each update or frame of animation, the monitor’s electron-gun starts at the upper–left corner and works itself across each scan-line (the blue line) until it reaches the right side. As it traverses from left to right, it uses the values passed to it in the pixel buffer to light up each screen pixel with the appropriate color. Once it reaches the right side, it returns to the left side (the green line) and begins the next row. This continues until it reaches the end of the last row where it then returns the upper–left corner (the red line) to start the next update. The time it takes to return to the top is the vertical retrace.

 

If we fail to synchronize our updates with the vertical retrace, our animation will suffer an anomaly called “tearing”. Tearing occurs when we try to render something to the monitor while the electron-gun is performing its vertical retrace. If we attempt an update during the vertical retrace, moving objects will appear to tear since only part of the update containing their new positions end up being rendered to the screen. Fortunately, Direct3D has way of freeing us from this problem by using a technique called double-buffering. With Double-buffering applications can not directly write to the monitors’ buffer, but instead have to synchronize with the vertical retrace by maintaining two separate pixel buffers. The two buffers are called the front–buffer and the back-buffer. The front-buffer is basically the monitor’s buffer and is used to render to the screen. The back-buffer is a special off-screen buffer used by the application. By writing only to the back-buffer and swapping the two buffers during the vertical retrace, we can prevent tearing since the monitor can only be updated by completely finished buffers and an accidental write to the front buffer while it’s being used is impossible.

 


Figure 5-13: Double-buffering in action

 

After we swap or flip the two buffers, we clear out the content of the buffer that used to be the front-buffer and begin rendering to it while the new front-buffer (formerly the back-buffer) is used to update the monitor.

 

Working with the Back-Buffer

 

Now that you understand why it’s important to perform double-buffering, we can cover the special methods defined by the Direct3D device, which actually allow us to work with these buffers properly. The methods are Clear, BeginScene, EndScene, and Present. As a demonstration of their usage and placement we’ll use the same vertex buffer (g_pVertexBuffer ), which we created earlier in the chapter. The following code demonstrates the proper way to our render triangle contained in the vertex buffer:

 

Listing 5-1: render function of the “vertex_buffer” sample

 

//-----------------------------------------------------------------------------

// Name: render()

// Desc: Render or draw our scene to the back-buffer and flip the buffers.

//-----------------------------------------------------------------------------

void render( void )

{

    //

    // Clear both the back-buffer and z-buffer by filling the back-buffer

    // with black pixels and the z-buffer with the value 1.0f.

    //

 

    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,

                         D3DCOLOR_COLORVALUE(0.0f,0.0f,0.0f,1.0f), 1.0f, 0 );

 

    //

    // Let Direct3D know that scene rendering is about to begin...

    //

 

    if( FAILED(g_pd3dDevice->BeginScene()) )

    {

        // TO DO: Respond to the failure of BeginScene

        return;

    }

 

    //

    // Create a simple world matrix to move the triangle down the Z axis

    // 5 units. If we don't do this, both us and the triangle will be sitting

    // at the coordinate system's origin and we won't be able to see the

    // triangle at all.

    //

 

    D3DXMATRIX mWorld;

    D3DXMatrixTranslation( &mWorld, 0.0f, 0.0f, 5.0f );

    g_pd3dDevice->SetTransform( D3DTS_WORLD, &mWorld );

 

    g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(Vertex) );

    g_pd3dDevice->SetFVF( D3DFVF_MY_VERTEX );

    g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );

 

    //

    // Let Direct3D know that scene rendering is finished...

    //

 

    if( FAILED(g_pd3dDevice->EndScene()) )

    {

        // TO DO: Respond to the failure of EndScene

        return;

    }

 

    //

    // Swap or flip the front and back buffers...

    //

 

    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

}

 

The sample application will call the render() function once per update and render or draw our simple triangle to the screen. The first thing we want to do is call the Clear method and wipe out the contents of the back-buffer before we start. This is necessary since the current back-buffer was the front-buffer during the last frame update. If we don’t clean it out, we will simply be rendering on top of the last frame’s pixels. Of course, this wouldn’t really be a problem if we knew for certain that every single pixel was going to be over-written, but it’s often easier to just clear the whole thing out instead trying to guarantee that this will happen under all circumstances.

 

HRESULT Clear( DWORD Count,

    const D3DRECT *pRects,

    DWORD Flags,

    D3DCOLOR Color,

    float Z,

    DWORD Stencil

);

 

We’ll set the first two arguments, count and pRects, to 0 and NULL respectively since we want to clear the entire buffer. If, for some reason, we didn’t wanted to clear the whole buffer we could perform a partial clear of one or more rectangular regions of the buffer by passing an array of D3DRECT structures along with the total count.

 

The third argument, Flags, is where we specify which buffers to clear. I say “buffers” (plural) since the Clear method is also used to clear out the z-buffer and stencil-buffer. For this sample, we’ll pass D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, which tells Clear that we want to wipe out both the back-buffer and z-buffer. We really don’t need to pass D3DCLEAR_ZBUFFER in this sample, but it’s a good habit to get into since we’ll need it cleared more often than not.

 

The z-buffer is used to guarantee that pixels belonging to geometry, which is further away, doesn’t overwrite pixels belonging to geometry, which is much closer. In other words, the z-buffer eliminates the need to pre-sort our triangles by distance from the viewer’s position. Before we had hardware accelerate z-buffers, 3D graphics programmers had to make sure to render their triangles in front-to-back order. If they didn’t render front-to-back, triangles that made up distant and possibly obscured objects would end up getting rendered on top of the triangles of much closer objects. Who would want to play a race game where triangles from the road kept popping up and blocking out triangles that make up the car’s hood? That would just look wrong!

 

The fourth argument, Color, is where we specify the color to be used to clear the back-buffer. Here, we simply set this to pure black by passing D3DCOLOR_COLORVALUE( 0.0f, 0.0f, 0.0f, 1.0f ). The argument’s actual type is D3DCOLOR (a.k.a DWORD), but I’ve always found that to be a strange type to use for color since most 3D graphics programmers treat colors as being composed of 4 floats, so I decided to be more traditional and use the D3DCOLOR_COLORVALUE macro to convert the color values.  The first three floats represent the separated red, blue, and green color components while the fourth float represents the color’s alpha value. Each value is considered normalized and must be set to something between 1.0f and 0.0f to be accepted as valid. For the first three floats (red, green, and blue) 1.0f means full-color while a value of 0.0f means none. The fourth float (alpha) is considered fully opaque at 1.0f and completely transparent at 0.0f. Here are a few examples:

 

D3DCOLOR_COLORVALUE( 0.0f, 0.0f, 0.0f, 1.0f ) // black – fully opaque

D3DCOLOR_COLORVALUE( 1.0f, 1.0f, 1.0f, 1.0f ) // white – fully opaque

D3DCOLOR_COLORVALUE( 1.0f, 0.0f, 0.0f, 1.0f ) // red – fully opaque

D3DCOLOR_COLORVALUE( 0.0f, 1.0f, 0.0f, 1.0f ) // green – fully opaque

D3DCOLOR_COLORVALUE( 0.0f, 0.0f, 1.0f, 1.0f ) // blue – fully opaque

D3DCOLOR_COLORVALUE( 1.0f, 1.0f, 0.0f, 1.0f ) // yellow – fully opaque

D3DCOLOR_COLORVALUE( 0.0f, 1.0f, 1.0f, 1.0f ) // aqua – fully opaque

D3DCOLOR_COLORVALUE( 1.0f, 0.0f, 1.0f, 1.0f ) // purple – fully opaque

D3DCOLOR_COLORVALUE( 1.0f, 0.5f, 0.5f, 0.5f ) // pink – semi-transparent

 

Making up custom colors in this manner can take a little getting used to, so if you don’t quite get it, feel free to play with these values in the sample and see what happens.

 

The fifth argument, Z, is the value that we want used when clearing the z-buffer. Again, this is a normalized value and needs to be in the range from 0.0f through 1.0f to be valid. We’ll simply pass 1.0f. This will reset the z value of each pixel to 1.0f, which represents the furthest distance that a pixel can be set to.

 

The last argument, Stencil, is used to set the value for clearing the stencil buffer. Since we are not using the stencil buffer, we’ll simply set it to 0.

 

Our next method to call is BeginScene. Nothing can be drawn or rendered using a Direct3D device until this method has been called and has returned successfully.

 

HRESULT BeginScene(VOID);

 

The BeginScene method gives Direct3D a chance to set things up internally for rendering. If for some reason Direct3D can not begin rendering right away, the method will fail and we can either opt to try again or abort our rendering efforts till the next frame. The method doesn’t take any arguments so there’s not much to cover here. To keeps things simple, our sample’s render function will simply return early if BeginScene fails for any reason. If BeginScene succeeds, we can begin setting up the scene and rendering.

 

Once we’re finished setting up the scene and rendering, we notify Direct3D that we’re completely done working on the current frame by calling the EndScene method.

 

HRESULT EndScene(VOID);

 

If this method returns successfully, the current scene has been queued up for rendering by the driver and all that remains to be done is to actually flip or swap the front and back buffers with a call to Present.

 

HRESULT Present( CONST RECT *pSourceRect,

    CONST RECT *pDestRect,

    HWND hDestWindowOverride,

    CONST RGNDATA *pDirtyRegion

);

 

The proper usage of the four of arguments defined by the Present method is well beyond this book, so all samples will simply pass four NULLs when calling Present. Please refer to the SDK’s documentation for more information.

 

At this point, you can continue your study of 3D primitives and Vertex Buffers by playing around with the second sample called, “3d_primitives”, which actually demonstrates how to use each of the six primitive types by creating, loading, and rendering from six different Vertex Buffers – one for each primitive type. As you’ll soon see, there’s really no difference between primitives when it comes to using them in conjunction with a Vertex Buffer. The only thing you really need to double-check is to make sure you’re allocating enough vertices to cover the total number primitives that need to be stored in each buffer.

 

Creating Geometry From 3D Primitives

 

The multi-colored triangle created by the “vertex_buffer” sample is a good starting place when learning about 3D primitives, but triangles are flat and uninteresting to look at and teach us nothing about how to create more complex objects out of primitives. Let’s put our new knowledge to work and try to build something a little more exciting like a cube which will actually be 3-Dimensional. You can follow along by checking out the code in the “cube” sample.

 

The first thing will do is define what each vertex of our cube contains by creating a Flexible Vertex Format code and vertex structure for it:

 

#define D3DFVF_CUBE_VERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE )

 

struct CubeVertex

{

    float x, y, z; // Position of vertex in 3D space

    DWORD color;   // Color of vertex

};

 

As you may have noticed, we’re going to use the same vertex lay-out as the triangle in the “vertex_buffer” sample. The vertex lay-out uses the D3DFVF_XYZ FVF and D3DFVF_DIFFUSE flags to tell Direct3D that each vertex that makes up our cube will contain a position, which is composed of three floats, and a diffuse color represented by the DWORD data type.

 

The next step is to decide on a 3D primitive to use so we can start defining the cube’s data. For our cube I’ve decided to define each side of the cube as a tri-strip using D3DPT_TRIANGLESTRIP. This may not be the most efficient way to define a cube, but it’s fairly easy to follow and allows me demonstrate other items as we progress.

 

Here’s our cube’s vertex data. Note how we’re defining the vertex data in a temporary array. Eventually, it will be loaded into a Vertex Buffer, but for now we’ll store the data like this so we can examine it closer.

 

CubeVertex g_cubeVertices[] =

{

    // Side #1 - red

    {-1.0f, 1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 ) },

    { 1.0f, 1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 ) },

    {-1.0f,-1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 ) },

    { 1.0f,-1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 ) },

 

    // Side #2 - green

    {-1.0f, 1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 ) },

    {-1.0f,-1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 ) },

    { 1.0f, 1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 ) },

    { 1.0f,-1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 ) },

 

    // Side #3 - blue

    {-1.0f, 1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 ) },

    { 1.0f, 1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 ) },

    {-1.0f, 1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 ) },

    { 1.0f, 1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 ) },

 

    // Side #4 - yellow (red + green)

    {-1.0f,-1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 1.0, 1.0, 0.0, 1.0 ) },

    {-1.0f,-1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 1.0, 0.0, 1.0 ) },

    { 1.0f,-1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 1.0, 1.0, 0.0, 1.0 ) },

    { 1.0f,-1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 1.0, 0.0, 1.0 ) },

 

    // Side #5 - magenta (red + blue)

    { 1.0f, 1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 1.0, 1.0 ) },

    { 1.0f, 1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 1.0, 1.0 ) },

    { 1.0f,-1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 1.0, 1.0 ) },

    { 1.0f,-1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 1.0, 0.0, 1.0, 1.0 ) },

 

    // Side #6 - cyan (blue + green)

    {-1.0f, 1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 1.0, 1.0 ) },

    {-1.0f,-1.0f,-1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 1.0, 1.0 ) },

    {-1.0f, 1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 1.0, 1.0 ) },

    {-1.0f,-1.0f, 1.0f,  D3DCOLOR_COLORVALUE( 0.0, 1.0, 1.0, 1.0 ) }

};

 

You should also note that even though we’re using a single array to store our cube data, the array actually contains six different tri-strips, one for each of the cube’s six sides. To make it easier to see which vertices belong to which side, I’ve broken up the array’s declaration with comments which delineate the vertex stream. After doing this, it becomes obvious that we use four vertices for each side.

 


Figure 5-14: Each side of the cube is basically composed of two triangles built from four vertices using D3DPT_TRIANGLESTRIP.

 

I know it may seem confusing at first as to how all these vertex points make a cube, so you may have to sit down for a moment and work it all out in your head or plot it onto some graph paper to convince your self, but I assure you it does, in fact, make a cube.

 

Now that we have the cube data defined, our next step is to use our Direct3D device object to create a Vertex Buffer to hold our data:

 

g_pd3dDevice->CreateVertexBuffer( 24*sizeof(CubeVertex),0, D3DFVF_CUBE_VERTEX,

                                  D3DPOOL_DEFAULT, &g_pVertexBuffer, NULL );

void *pVertices = NULL;

 

g_pVertexBuffer->Lock( 0, sizeof(g_cubeVertices), (void**)&pVertices, 0 );

{

    memcpy( pVertices, g_cubeVertices, sizeof(g_cubeVertices) );

}

g_pVertexBuffer->Unlock();

 

Take careful note of how our call to CreateVertexBuffer makes sure to allocate enough space for the 24 vertices that define our cube. It’s Ok to allocate too much space, but not enough and we’ll have a problem when we attempt to memcpy the contents of our temporary array into the Vertex Buffer.

 

Finally, the only thing that remains is to render our cube. To set things up, we set our cube’s Vertex Buffer as the stream source with a call to SetStreamSource and to help Direct3D interrupt our data stream correctly, we set our custom FVF code with a call to SetFVF. Once that’s done, all we have to do is call DrawPrimitive to render our cube’s six sides.

 

g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CubeVertex) );

g_pd3dDevice->SetFVF( D3DFVF_CUBE_VERTEX );

 

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  4, 2 );

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  8, 2 );

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 12, 2 );

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 16, 2 );

g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 20, 2 );

 

As I mentioned earlier, it’s important to note that our cube defines its six sides as six separate tri-strips. This means that we'll need to call DrawPrimitive six times to render the whole cube. And since we stored all the vertices for the six sides in the same Vertex Buffer, we don’t need to call SetStreamSource for each side, but we will need to move the start vertex up for each call we make to DrawPrimitive. This is how we can store and access multiple pieces of geometry contained in a single Vertex Buffer.

 


Figure 5-15: The final product.

 

 

© 2003 Kevin R. Harris All rights reserved.

Legal Disclaimer and Copyright Notice