[ 32bits.co.uk ] Coding Direct3D for real people

32Bits - Coding for real people!

.:[ Please always use http://www.32bits.co.uk to access the site. Thanks! ]:.

32Bits main page Introduction to the site The tutorials index Our releases The license for content on this site Public forum for questions and feedback We provide professional services Contact us

 

Last updated: 10:55 AM Monday October 2008

[ View the Corrections & Additions to this Tutorial ]

welcome.gif (1805 bytes)

Here is where we start using the 3D side of the D3D API. There is a pretty steep learning curve to begin with, but once you get past the initial learning stage you'll pick the rest up nice and quickly. This tutorial will show you the "Hello World" equivalent of the 3D API world - drawing a single triangle on screen.

You'll have to bear with me on this tutorial, as there are far too many principles to introduce at once to make it nice and logical. I'll start with a vague overview of the process, and then show you the code step by step. It's a long tutorial, we've got a lot of new ground to cover!

The first thing we need to discuss are the principles behind the D3D 3D pipeline. Let's start with "What's a pipeline?" :). Imagine the 3D side of D3D as a magic black box. In the top (or left, in the diagram below) we throw all our information about the 3D objects we want to display on-screen, and out the bottom (right) pops our scene, neatly arranged just as we want it. Microsoft provide the following diagram to teach you exactly what this pipeline does:


Fig 1.1: The complete Fixed Function and Programmable Transformation and Lighting Pipeline.


There you have it. The Fixed Function and Programmable Transformation and Lighting Pipeline, or "T&L Pipeline" for short. Don't you just love the way Microsoft makes things ten times harder than they need to be. Let's run through it quickly, from left to right. First, you dump in all the data about your 3D object. D3D then chops it all up into little bits, and figures out if any of the data needs to be fiddled with (Fixed Function/Programmable Pipeline). It then figures out which parts of the object are visible (Clipping & Culling), and pulls out the first section of the object. Next, it applies a pixel shader to make low-level graphical changes. Finally D3D makes sure the current part of the object really should be output onscreen (Depth test, Stencil test), adds some fog effects and blending if required, and after all that it draws the object part on screen. Woah. That is the complete pipeline, it all it's technical glory. The bad news is that during your D3D coding life you'll use most, if not all of these pipeline components. The good news is that while you're learning, this is all you need to know:


Fig 1.2: The T&L Pipeline you actually need to know.


Doesn't that make you feel much better! Now that we only have 2 sections of the pipeline to worry about, we can concentrate on finding out exactly what they do. Lets look at the detail behind Fig1.2 in more detail. There are 6 seperate steps to drawing even the simplest 3D object on screen:


Fig 1.3: 6 Steps of drawing

Note that these are the logical steps to drawing a 3D object. In other words, if we wanted to write the simplest app possible, we would do the steps in the order above. As you'll see shortly, we can change the order of some of these steps.


Let's start with understanding the input in Fig 1.2 - Vertex or Primitive data. We're going to come back to primitives in a second, but first I want to talk about vertices. A vertex (or vertice, depending on whether you're American, English and the time of day) is simply a point in space, defined by it's X,Y and Z coordinates.


Fig 1.4: A vertice. Woot.


There we have a vertice positioned at 3 units on the X axis, 2 units on the Y axis and 4 units on the Z axis. As you can see, this vertice only has 3 "properties" - it's X, Y and Z positions. Now, let's say we wanted to make a triangle out of vertices:


Fig 1.5: A triangle from some vertices.


That's simple enough, we should now be in agreement that we can make a triangle out of any 3 vertices (providing the vertices do not lie on a plane, ie: in a straight line on any axis). If you've played pretty much any recent game, you'll also know that all 3D objects are built up using loads of triangles (we'll cover this in a second when we come back to primitives). So, the question is how do we supply this information to D3D to draw us triangles on screen to make up a 3D object? The process is fairly simple, and it all starts off with a "vertex format".

D3D is a very flexible API, with provisions for lighting, texturemapping, materials and much more. As we have established, all 3D objects are made up of triangles. So, if we're going to map a texture onto our triangle in fig1.5, the way to do it is to tell D3D which part of the texture we want to map onto each vertice of the triangle. We now have an additional property for each vertice - as well as an X,Y,Z coordinate we also need a value to tell D3D which bit of texture should go on the vertice. In data terms, we've doubled the amount of info we need to provide for each vertex, and thus near doubled the size in memory of each vertex. That wouldn't be so much of a problem, except there are loads of possible properties for a vertex (which we won't go into now, for sanity reasons!), and most of the time you only require a small subset of them. Why specify texture values if we don't want to texturemap our triangle?

Fortunately the DirectX designers came up with a clever way round this. It's called "Flexible Vertex Format", or FVF for short. In a sentance, it allows you to use only the vertex properties you need. First of all you create a struct containing members for the data you require, then you create a custom #define to tell D3D exactly what properties you want to use. This will make more sense in code:

typedef struct _tagCustomVertex
{
    float x,y,z;
} CUSTOMVERTEX;

#define CUSTOMFVF (D3DFVF_XYZ)


First we create a custom struct containing 3 float vars representing our XYZ coordinates. Then we create a custom #define that we later use to tell D3D we're only giving it XYZ coordinates and nothing else (in this instance, we just make our define equal to the D3D define D3DFVF_XYZ - the define representing vertice coordinates). This would be perfectly sufficient for us to draw a triangle on screen with, as you'll see shortly. Here's another example, this time with some information for texturemapping:

typedef struct _tagCustomVertexTextureMap
{
    float x,y,z;
    float texcoord1, texcoord2;
} CUSTOMVERTEXTEXTUREMAP;

#define CUSTOMTEXTUREMAPFVF (D3DFVF_XYZ | D3DFVF_TEX1)


Here we've added 2 floats for our texture information, and made our define equal to D3DFVF_XYZ (vertice flag) OR'd with D3DFVF_TEX1 (the texturemapping flag). Hopefully you can now see how these FVF systems work. So, we've created a custom structure to hold our data, and a define to tell D3D what we're passing it - that's steps 1 and 2 of Fig 1.3. Next we actually need to pass the data to D3D. This is done with the help of a vertex buffer.

There are lots of different D3DFVF_ defines provided by D3D as flags for different effects. For example, D3DFVF_XYZ, D3DFVF_TEX1 and D3DFVF_DIFFUSE are just a few of the possible flags we can OR to our custom vertex define. Don't worry about the different possibilities just yet, I'll introduce each flag in later tutorials as we require it. For now, just concentrate on understanding how the custom vertex struct and the custom define work together.


A vertex buffer is chunk of memory allocated specifically to store arrays of vertices. It has a few extra properties that enable D3D to manage the memory better, for example if your vertice data is constantly changing, but it's basic use remains the same. We create a vertex buffer, and an array of our custom vertex structs. We fill out the array with data, and simply copy it directly into the vertex buffer. That's all!

Now that we've set up all the information D3D needs to draw our triangle, we need to actually tell it to do the drawing. We do this by telling D3D to use our vertex buffer as the data source, our custom #define as the details on how to read the data, and which primitive to use to do the drawing with. Which brings us back to Fig 1.1 and the question "What is a primitive?".

A primitive is a simple shape type made from triangles. 3D gfx cards are built and optimised to draw triangles as quickly as possible, and triangles are the only input they expect. Fortunately there are a lot of things you can do with a little triangle, and D3D supports quite a few types of primitive. Let's look at a practical example. Take the following shape:


Fig 1.6: A random shape.


To draw this on screen, we first need to divide it up into triangles that D3D can handle:


Fig 1.7: A random shape thatD3D can handle.


Now we've divided our shape up into 7 seperate triangles, we have 21 clear vertices that makes up our random shape:


Fig 1.8: 21 vertices of the random shape.


Assuming we gave each vertice of each triangle an on-screen co-ordinate, we could now draw this shape in 7 triangles and 21 vertices. Drawing a shape in this method is called a Triangle List. Burn that phrase into your brain! When drawing these types of primitives you use the D3DPT_TRIANGLELIST define. You'll find out exactly what to do with this define in a moment.

As you're no doubt aware, 3D graphics cards have a maximum amount of polygons they can "shift" (display on screen). The more polygons the card has to display the longer it takes to process them, thus leading to a slower frame rate. However, it's not actually the polygons that are the trouble so much, it's the vertices. Take a close look at figures 1.7 and 1.8. If we split the shape up unto 7 seperate triangles, we're using 21 vertices. However every triangle in our shape shares at least 2 vertices with another triangle:


Fig 1.9: So many shared vertices!


The red circles show just how many vertices are shared between triangles. It's a shame that we're duplicating so many vertices here. If only there was a way to share vertices between triangles and cut down on the number required. Oh, of course there is!


Fig 1.10: Ah, much better


This may not be immediately obvious at first, so get a pen and point to V1. Move your pen down to V2, then up to V3. You've just drawn triangle 1. Now move from V3 to V4. You've just drawn triangle 2! D3D takes the first 3 vertices to make triangle 1. We then use the last vertice of triangle 1 (V3) as the first vertice of triangle 2. When you get to V4, we now have the second vertice of triangle 2. It then uses V2 to complete the triangle, and the process begins again for triangle 3. This is called a Triangle Strip (or TriStrip for short), because you are literally passing D3D a long strip of triangles where each triangle is connected to the previous one by 2 vertices. Here's a simpler example - how to draw a rectangle using a TriStrip:


Fig 1.11: Rectangles by tristrips.


When drawing these types of primitives, you use the D3DPT_TRIANGLESTRIP define. That's all well and good, but what if you wanted to draw a 3D circle? Using TriStrips or Lists, you'd end up with the following:


Fig 1.12: Poor version of a circle :)


Ok, it's a very bad circle, but you get the point. You can't create smooth curves using triangles, but if you use a large amount of them in a small area you can produce the effect of a smooth circle. But count the number of vertices and triangles we need in there. Using the primitives we know so far, it would be near impossible to generate smooth curves. There is, of course, a solution:


Fig 1.13: Less poor version of a circle!


As you can see, it's much neater and more circle-esque than the TriStrip/List solution in Fig 1.12. It's called a Triangle Fan, and works along the same lines as a TriStrip. Each triangle shares a common start vertice, and uses the previous triangle's vertice to make up 2 of the 3 vertices. Therefore you only need to specify one vertice for every triangle. The handy thing about fans is that they are easy to generate programatically. If you know the radius of the circle you wish to create, you can generate the vertice points rather than set them manually. When drawing these primitives, you use the D3DPT_TRIANGLEFAN define.

There are some other primitive types such as line lists, line strips and point sprites, but you now know the main primitives that we'll be using. I've given you a lot of theory here, some of it probably a little confusing, so it's time to see the code in action. Here's what we'll be achieving:


Fig 1.14: Our triangle. We're not going backwards, honest.


It may not look much, but it's a major step! Here's the code for this tutorial. As we're starting on a brand new topic I've put all the class based code to one side and reverted to one single .cpp for clarity (with our helper D3DFuncs.cpp/.h).

[ Download complete VC6 workspace as .zip ]

WorldTri.cpp
[ View source code in this window ] [ View source code in a new window ] [ Source code in plain ascii .cpp file ]


Let's go through it step by step. Starting at the top of WorldTri.cpp, we create some global vars:

LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer;

typedef struct _tagSimpleVertex
{
    float x,y,z;
    float rhw;
} SIMPLEVERTEX;

#define FVF_SIMPLEVERTEX (D3DFVF_XYZRHW)


First up we create a global vertex buffer, which is a COM pointer to the IDirect3DVertexBuffer9 interface. For the purposes of our code it needs to be global as it will be required in the GameInit() and Render() functions, but it could just as easily be a class member. Next we create our custom struct, as we discussed earlier in the tutorial. To make the learning process easier, I've used what's called "Pre transformed and lit vertices". In other words, the X and Y vertex values for our triangle specify a pixel position on screen. This simply means if we set a vertice's X value to 100, and it's Y value to 150 the vertice will be positioned exactly 100 pixels from the left edge of the screen, and 150 pixels from the top of the screen. Specifying vertices in this way is called working in "Screen Space", or "RHW".

To enable pretransformed and lit vertice processing, we need to supply D3D with a "RHW" value. The technical description of the RHW float is as follows (from the DX9 SDK):

"The system requires that the vertex position you specify be already transformed. The x and y values must be in screen coordinates, and z must be the depth value of the pixel to be used in the z-buffer. Z values can range from 0.0 to 1.0, where 0.0 is the closest possible position to the user, and 1.0 is the farthest position still visible within the viewing area. Immediately following the position, transformed and lit vertices must include a reciprocal of homogeneous W (RHW) value. RHW is the reciprocal of the W coordinate from the homogeneous point (x,y,z,w) at which the vertex exists in projection space. This value often works out to be the distance from the eyepoint to the vertex, taken along the z-axis."

At this point I'll be absolutely honest. When I was learning D3D, I spent many hours trying to understand exactly what that paragraph meant. I eventually came to the conclusion that unless I wanted to spend a very long time figuring it out, I needed to accept that "RHW" meant I was working in screen space where vertice coordinates were just a pixel position on screen. It's up to you whether you choose to accept it, or figure it out for yourself.

The last piece of our custom vertex puzzle is the custom define. In this case as we've chosen to work in Screen space, we need to tell D3D to do the same by specifying the D3DFVF_XYZRHW flag (there's that RHW again!). You may be wondering why I've chosen to create a new define that is exactly the same as an existing define. The simple answer is "for clarity". If we always pair a custom struct with a custom FVF, we'll never need to worry whether we've got the right flags defined.

That's the global vars defined, so let's take a look at the initialisation in GameInit():

// Create an array to hold our triangle's 3 vertices:
//      v1
//      /\
//     /  \
// v0 /____\ v2


SIMPLEVERTEX Triangle[3];
Triangle[0].x=50.0f;
Triangle[0].y=200.0f;
Triangle[0].z=0.0f;
Triangle[0].rhw=1.0f;

Triangle[1].x=150.0f;
Triangle[1].y=50.0f;
Triangle[1].z=0.0f;
Triangle[1].rhw=1.0f;

Triangle[2].x=250.0f;
Triangle[2].y=200.0f;
Triangle[2].z=0.0f;
Triangle[2].rhw=1.0f;


First we create an array of 3 SIMPLEVERTEX's, one for each point of the triangle. We then fill out each of the array members, starting with the bottom left, then top middle, and finally bottom right. Remember, we're working in screen space so vertice 0, for example, will be 50 pixels in from the left of the screen (or window, if we're running in windowed mode), and 200 pixels down from the top. We're leaving all the Z axis values at 0, as they aren't of any use in RHW. Last of all, we set the dreaded RHW value to 1.0f, for reasons that are not for us to worry about. That's our triangle sorted, now lets put it in a vertex buffer:

// Next, create our vertex buffer using the properties of our custom struct and FVF
rslt=g_pDevice->CreateVertexBuffer(sizeof(Triangle), 0, FVF_SIMPLEVERTEX, D3DPOOL_DEFAULT,
                                   &g_pVertexBuffer, NULL);


Before we can actually copy our vertex data into a vertex buffer, we need to create the buffer itself. We do this by calling the IDirect3DDevice9::CreateVertexBuffer() COM method. It's prototyped as follows:

HRESULT CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool,
                           IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pHandle);


Some of these should be familiar to you, as we've looked at them when we wrote the Sprite engine. Parameter 1 is the size of the vertex buffer in bytes you wish to create. For our purposes we need a vertex buffer big enough to hold our entire Triangle[] array, so we use the sizeof() operator to get the byte size required. Parameter 2 allows us to set some more advanced characteristics of the vertex buffer such as read-only or dynamic allocation. We don't require those functions so we simply set this parameter to 0. Parameter 3 are the FVF flags that tell D3D what data our custom struct contains, so we pass it our custom define. Parameter 4 is the memory pool we wish to place the vertex buffer in, in this case the default pool as specified by D3DPOOL_DEFAULT will be fine. If you remember back to tutorial 5 of series 2, D3DPOOL_MANAGED would also be an option here to save us restoring the vertex buffer after a lost device. Parameter 5 is the address of the pointer to our vertex buffer we created at global scope - g_pVertexBuffer. Parameter 6 is reserved for future use, and the SDK states that it should alwats set to NULL.

The CreateVertexBuffer() method has gained an additional parameter in DX9 - the final HANDLE* parameter. This is another Microsoft special, where they put a parameter in and mark it "reserved for future use". It should always be set to NULL.

Once we've sucessfully created our vertex buffer, we can lock it and copy the data from the Triangle[] array into it:

BYTE* pVerticeLock=0;
rslt=g_pVertexBuffer->Lock(0, sizeof(Triangle), (void**)&pVerticeLock, 0);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to lock Vertex Buffer."); }

// ...and copy our array of vertices straight into it
CopyMemory(pVerticeLock, &Triangle, sizeof(Triangle));


First we create a BYTE pointer, which we'll use in a second to copy our data. We then call the IDirect3DVertexBuffer8::Lock() COM method to lock the vertex buffer so we can copy data into it. It's prototyped as follows:

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


Parameter 1 is the offset into the vertex buffer you wish to lock. This is handy if you have a massive vertex buffer but only wish to change a few parts of it. In our case the vertex buffer is the same size as our array of vertices so we want to lock the entire buffer from the start, therefore we supply 0 for the offset. Parameter 2 is the size in bytes we wish to lock. As we'll be copying the entire Triangle[] array, we set this to the byte size of the array. Parameter 3 is the address of a pointer which will be filled with the memory location of the locked vertex buffer (or the section of it, if we'd specified values for params 1 and 2). The practice of passing the address of a pointer (functions prototyped with parameters of pointers to pointers - BYTE** for eg) as a parameter to a D3D method is a common one, so get used to checking if the method you're about to use requires a normal pointer, or a pointer to a pointer. The final parameter is used to tell D3D how to lock the vertex buffer - whether to throw the current information away, for example. We don't require anything special here, so we just pass a 0.

If IDirect3DVertexBuffer8::Lock() doesn't return a failed HRESULT, we're ready to copy our vertex data directly into the vertex buffer. We do this with a plain-and-simple memory copy. I use CopyMemory(), but you're free to use memcpy() or any other function you prefer. CopyMemory() isn't a D3D function, but I'll take a quick detour and show you the prototype:

VOID CopyMemory(PVOID Destination, CONST VOID* Source, SIZE_T Length);


Parameter 1 is a pointer to the destination location of the copy. Parameter 2 is a pointer to the source location of the data, and parameter 3 is the size in bytes of data to copy.

Now that we've copied our data into the vertex buffer, there is one last and very important thing we need to do:

g_pVertexBuffer->Unlock();


If you forget to unlock your vertex buffer you will have many, many problems and see lots of wierd errors, so make sure you unlock it!

We've created our custom struct and FVF, we've made a vertex buffer, filled it with vertice data and unlocked it. All that's left to do is draw the triangle, so let's take a look in our Render() function. I omitted the errorchecking code for clarity, so note that each of the next 3 lines of code return HRESULTs and would benefit from some errorchecking.

// Tell D3D we want to use our vertex buffer as the "source stream"
g_pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(SIMPLEVERTEX));


First up we bind our vertex buffer to Stream 0. A stream is used to link a vertex buffer to the actual primitive drawing methods of D3D8. As we're passing a custom array of data to D3D, it's the stream's job to take each array member, compare them to the custom FVF define and then pass each part of the array member (position, texture information, colour etc) to the primitive drawing methods in a format they can handle. The method is prototyped as follows:

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


Parameter 1 is the stream number we wish to bind the vertex buffer to. Until we start writing some pretty advanced code this parameter will always be zero, as we have no need to process more than one stream. The second parameter is a pointer to the vertex buffer we wish to bind to the stream. The third parameter is the offset in bytes from the beginning of the vertex stream to the start of our vertex data. Not many cards support this and there is no real need to use it (plus the method of rendering vertices I'm showing you here doesn't require this offset), so setting this parameter to 0 to indicate "no offset needed" is just fine. The final parameter is the data "stride". In English, this means "how big in bytes is one complete vertice with all it's data". As we used our custom SIMPLEVERTEX struct to represent one vertex (we made an array of 3 of them, remember!), we simply pass the byte size of the struct.

As a point of interest, the SDK docs tell us that after we're done with out vertex buffer, we should call SetStreamSource() with NULL parameters for params 2 and 3. The reason behind this is SetStreamSource() increments the reference count on the vertex buffer every time you attach a buffer to a stream, and decreases it every time it's detached from a stream. Therefore the thinking is that if you don't call SetStreamSource() with NULL parameters you'll experience a memory leak. I've never actually done this, as if you release your interfaces correctly you don't actually see this memory leak happen. It's your choice whether you follow the SDK's advice or not!

A double comment! This method has gained an additional parameter since DX8 - the UINT OffsetInBytes parameter. Seeing as we don't need it and won't be using it, that's all you need to worry about!


Next up, we tell the stream which FVF flags we've used in our custom struct with the following line of code:

// Tell D3D we want to use our custom FVF define
g_pDevice->SetFVF(FVF_SIMPLEVERTEX);

Another little change. To accomodate the new changes to the pipeline & shader system in DX9, Microsoft renamed the old DX8 SetVertexShader() method to SetFVF() in DX9. It does exactly the same thing in exactly the same way.


Nothing fancy behind this code. We're just telling D3D (specifically the stream) to use our custom FVF to interpret our vertex data. Finally we're ready to get D3D to draw the triangle on screen:

// And finally tell D3D to draw our triangle!
g_pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);


Here's the prototype for IDirect3DDevice9::DrawPrimitive():

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


Parameter 1 is the type of primitive you wish to draw. As we discussed above, some of the possible values here are D3DPT_TRIANGLELIST, D3DPT_TRIANGLESTRIP and D3DPT_TRIANGLEFAN. Choose the correct one for the object you're trying to create, but also remember that your vertex data has to fit into the primitive type you choose. If you specify your vertices with a TriStrip in mind, but pass DrawPrimitive() a D3DPT_TRIANGLEFAN parameter, you'll see some unexpected results! Parameter 2 is the index of the first vertex in the vertex buffer you want D3D to begin drawing from. For our purposes we only have 3 vertices, so we want to start drawing from vertex 0 (remember, it's a zero based index). Parameter 3 is the number of primitives we want to draw. I put primitives in bold there as on more than one occasion I've forgotten what I was doing and put the number of vertices instead!

There is one final piece of code we mustn't forget. Vertex buffers are COM interface pointers, so we need to add the following code to GameShutDown():

if(g_pVertexBuffer)
   g_pVertexBuffer->Release();


You should know what this code does by now!

Wow, you've just waded your way through your first bit of true 3D code using D3D9. It probably seems a lot harder than it actually is, so here is a quick recap of the steps you need to draw a triangle on screen:

1. Create a custom struct for your vertices
2. Create a custom FVF define that represents your custom struct
3. Create an array of your custom vertex struct
4. Fill out the array with vertex data
5. Create a vertex buffer large enough to hold your vertice array
6. Lock the vertex buffer and copy the vertice array into it
7. Unlock the vertex buffer
8. Use SetStreamSource() to attach the vertex buffer to Stream 0
9. Use SetFVF() to "activate" your custom FVF define
10. Call DrawPrimitive() to draw your primitive
11. When done, release the vertex buffer interface pointer

Print these steps out and learn the code behind them, we'll be using it a lot in the tutorials to come!

Before we wrap up, I just want to show you a common problem you'll stumble across. You'll write your code, check it and build it, and everything will look fine. However, as soon as you run it you'll see the following:


Fig 1.15: Interesting, but not quite right.


This is fairly common, so get used to seeing it. It generally means one of a few things, but at this stage it's more likely to mean "Doh, you cleared a surface you really shouldn't have", or "You've totally screwed the drawing operation up". Here is where our good quality setup code becomes handy. If you run D3D fullscreen, you've got no chance of debugging to find out what the problem is. However run it in a window and set a breakpoint just before DrawPrimitive(), you'll see the following output in your debug window when is DrawPrimitive() executed:


Fig 1.15: I wonder if that's bad.


As we discussed before, the stride is the size of one complete vertice with all it's data - if the stride is wrong, D3D can't process vertices properly. As this error is pointing towards an invalid stride value, the likely cause is an error in the SetStreamSource() method call. As it turns out, to produce this error I put an invalid value (a random int) in the stride parameter. The moral of the story is, if you get some blatantly corrupt output run your app in windowed mode and check the debug output.

I know that screenshot is for DirectX 8, but it's identical for DX9. Honest!

We've covered a lot of ground in this tutorial, so I suggest you sit back and take it easy for a bit! Have a play with the source code and see what happens when you change different parameters and code. Make sure you completely understand this tutorial, as we'll be building on it over the next few tutorials. Meanwhile I'll leave you with some exercises! Don't worry if you can't do exercise 2 & 3, we'll be covering them in detail in the next tutorial.

Basic
Exercise 1: Play around with the values of the Triangle[] array and see what happens.

Intermediate
Exercise 2: Primitives drawn with a triangle list don't need to be adjacent to each other. Add some more vertices to the Triangle[] array, update the rest of the code and see what else you can draw on screen. You'll need to make sure you've put the correct values for all the method parameters.

Advanced
Exercise 3: See if you can get a Triangle Strip and a Triangle Fan drawn on screen.

 

 

 

 

Click here to send feedback, bug reports, comments and ideas etc!

 


Corrections & Additions

 

13/06/02 - Krešimir Kiš pointed out a typo where I'd mistakenly used D3DPT_TRIANGLELIST when D3DPT_TRIANGLESTRIP was the correct define (below Fig 1.11). Thanks!

27/08/03 - Bret Faller pointed out exactly the same thing, because for some reason the html on the website wasn't updated but my local copy was. Oddness. Thanks!

20/06/04 - Karl Rausing noticed I'd missed off a void** cast in the tutorial text, but not in the downloadable code. Cheers Karl!