C++ Graphics Directx

26
Greg Dolley’s Weblog A Blog about Graphics Programming, Game Programming, Tips and Tricks Home About All Articles Contact Archives June 2008 February 2008 January 2008 December 2007 November 2007 October 2010 MTWTF S S « Jun 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Blog Stats 384,192 hits Subscribers Twitter Testing new twitter on Droid v2.2 1 month ago just joined a video chat with 16 other people at http://tinychat.com/shanselman #tinychat 5 months ago C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/ 1 of 26 10/7/2010 11:01 AM

Transcript of C++ Graphics Directx

Page 1: C++ Graphics Directx

Greg Dolley’s Weblog

A Blog about Graphics Programming, Game Programming, Tips and

Tricks

HomeAboutAll ArticlesContact

Archives

June 2008February 2008January 2008December 2007November 2007

October 2010

M T W T F S S

« Jun

1 2 3

4 5 6 7 8 9 10

11 12 13 14 15 16 17

18 19 20 21 22 23 24

25 26 27 28 29 30 31

Blog Stats

384,192 hits

Subscribers

Twitter

Testing new twitter on Droid v2.2 1 month agojust joined a video chat with 16 other people at http://tinychat.com/shanselman #tinychat5 months ago

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

1 of 26 10/7/2010 11:01 AM

Page 2: C++ Graphics Directx

Subscribe

Subscribe Me! (via RSS)

I Want Email Updates

Categories

.NET Graphics2D Graphics3D Graphics3D MathematicsC# ProgrammingC++ GraphicsC++/CLICool StuffDirectXGame ModdingGeneral ProgrammingMiscellaneousMobile GraphicsOpenGLQuake 2 C++ .NET PortQuake 3 C++ .NET PortTips and Tricks

Blogroll

AI Game DevCoding4FunFreeBSD VPS'sGarry Dolley’s BlogWordPress.org

Archive for the ‘C++ Graphics’ Category

DirectX 9 C++ Graphics Tutorial Part 3 – Drawing a Simple

3D Room

Posted by gregd1024 on June 15, 2008

Welcome to part three of the DirectX native C++ tutorials. In this part we’re going to look at drawing a set of3D triangles. These triangles will make up a cube and the camera will be positioned inside of it (like you’restanding in a square room). While the last tutorial technically had a 3D triangle – after all, we specified zcoordinates for every vertex – the scene was not truly 3D as far as DirectX is concerned. Try changing the zcoordinate for any of those vertices and you’ll see what I mean – the triangle still doesn’t look like it’s in

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

2 of 26 10/7/2010 11:01 AM

Page 3: C++ Graphics Directx

perspective.

Requirements

The tutorial sample code was built and tested with Visual Studio Express 2008. However, using the code“as-is” in Visual Studio 2005 should work too.

You’ll need the DirectX 9 SDK for compiling the sample code. Use this link for downloading: click here.

Tutorial Source and Project Files

Download the project files, binaries, and source with this link:

Download C++ Sources and Binaries [~15k]

Getting Started

DirectX needs three matrices in order to render a true 3D scene – a world transformation matrix, a cameraview matrix, and a projection matrix. If you’re unfamiliar with mathematical matrices, read the next sectionfor a brief introduction. Those already seasoned in this subject can just skip it.

Introduction to Matrices

Matrices are basically mathematical tables used in linear algebra. They look like a grid and contain numbers.An example is shown below:

This is known as a 4-by-4 matrix since it has four columns and four rows. It doesn’t necessarily have to be asquare – an m-by-n matrix is quite common. However, the world, view, and projection matrices are allsquares (4×4).

Just like regular numbers, matrices can be multiplied together or added to one another. A matrix like the oneabove is known as an identity matrix because of this special property: some matrix (let’s call it “M”)multiplied by an identity results in the same M matrix.

For this tutorial I’m going to show you how to multiply a m-by-m matrix by a m-by-1 matrix. If you’d like toknow about the other rules for matrix addition and multiplication, search for “matrix mathematics” onWikipedia. For this tutorial, however, it would only be helpful to know the procedure behind multiplying asquare matrix by a one column matrix. Here’s how it would be done given a 2-by-2 and 2-by-1 matrix:

A 3-by-3 matrix multiplied by a 3-by-1 matrix would look like this:

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

3 of 26 10/7/2010 11:01 AM

Page 4: C++ Graphics Directx

For the sake of discussion, let’s call the bigger matrix “A” and the one column matrix “B.” The values in eachrow of A are multiplied by the values in B and the results are summed making a matrix with the samedimensions as B.

So why is this useful? Well, for one, we can represent a lot of common formulas in a table format as opposedto writing them out long-hand. For example, the regular way of expressing the 2D point rotation formula isthis:

Now if we put the sine and cosine coefficients into a 2-by-2 matrix and put the x and y coordinates into a2-by-1 matrix, we can represent the above formula like this instead:

Try multiplying these two matrices as described above and you’ll find that it evaluates to exactly the same asthe long-hand version. In other words, we can rotate any 2D point just by multiplying it with this 2-by-2matrix! The same holds true for 3D points – we multiply them against a 3-by-3 matrix to get the newly rotatedpoint (however, the 3-by-3 rotation matrix is much more complicated than its 2D counterpart as you’ll seeshortly).

The reason that matrices are used in many 3D graphics applications, and what makes them unique, is the factthat fairly complex mathematical calculations can be simplified with just one or two matrix computations. Forexample, if you wanted to rotate a line segment around the z-axis in 3D space, you could do it via abrute-force method – apply the same long-hand trigonometric rotation equations to each point:

Note that the z component stays the same. To take the resulting vector and then rotate it around the y-axis,you’d use the same equations but apply them to the x and z components of each point:

Note that this time the y component stays the same and that x’ (the result of rotation #1) is used to calculatethe new position instead of the original x. To do a third rotation, around the x-axis, the same equations wouldapply except Zrot and Yrot would be used as inputs (Xrot would stay the same).

Now here’s the drawback to doing it brute-force versus using a matrix: we’re always rotating around one ofthe three coordinate system axes. What if we want to rotate a point around some arbitrary vector instead of

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

4 of 26 10/7/2010 11:01 AM

Page 5: C++ Graphics Directx

the x, y, or z axis? Using the long-hand equations to do that gets extremely complicated. However, there isone kind of matrix you can construct that, if multiplied by a 3-by-1 matrix representing a point, will result in anew 3-by-1 matrix containing values for the rotated point!

In case you’re wondering, this special type of matrix is called an angle/axis matrix and it looks like this:

It may look scary at first, but you really only need to plug in two things: the components for vector V (whichis the vector you want to rotate the point around) and Θ (the angle you want the point rotated). Note,however, that vector V must be normalized (it’s length must equal 1) for this matrix to work.

There’s another special property to this matrix – after plugging in your angle/vector combination andcalculating the number values for each element, those numbers actually represent vectors themselves – eachcolumn corresponds to a vector (so three total). These three vectors have one special property – they’realways perpendicular to each other. That’s right, no matter what angle or V vector you plug in, you’ll alwaysend of with three vectors that are aligned just like the x, y, z coordinate system axes. But there’s more! If youwere to rotate the coordinate system axes around vector V by your angle, the new coordinate system axisvectors would match the vectors contained in each column of this matrix! Can you see now what this matrixis really doing?

It’s called an angle/axis matrix because you’re actually calculating how the coordinate system axes would beoriented if you rotated them by a given angle around a certain vector. Considering these properties, you couldrewrite the above matrix like this:

The X vector, column one, holds the new x-axis after rotation. Column two, vector Y, holds the new y-axisafter rotation. And as you can probably guess, column three, vector Z, holds the new z-axis after rotation.

Rotation is not the only type of operation that can be done with matrices – you can also translate a point(move it to a new position). A typical translation matrix looks like this:

The Tx, Ty, and Tz components in the fourth column determine how much to move along the x, y, and z axesrespectively. Since this is a 4-by-4 matrix, you need to change the dimensions of the point matrix into 4-by-1.You do this by just inserting 1 into the fourth row. In other words, the entire operation would look like this:

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

5 of 26 10/7/2010 11:01 AM

Page 6: C++ Graphics Directx

Now you’re about to see the second beauty of matrices and why they’re really used in 3D graphics: if youwant to rotate and translate a point, you can actually combine the rotation matrix with the translation matrix!The new matrix becomes:

With just this one matrix you can specify not only how much to rotate a point, but also how to translate thatpoint as well! A matrix which combines rotation and translation is called a “transformation” matrix. As youcan probably guess, this type of matrix moves a 3D world around the player.

Now that you’ve had an introduction to matrices and how a transformation matrix works, we can move on tothe rest of the tutorial.

About the Code

As usual we’re going to build off of the example program from the last tutorial. Details about creating awindow, setting up DirectX, and drawing a 2D triangle will not be covered here. If you need to know aboutthose things, then I suggest reading the previous tutorial before moving on. Also, the example program for thissegment uses DirectInput to capture keystrokes from the user. I won’t be covering details on DirectInput, butthose parts of the code are clearly marked.

Also, unlike some of the previous posts, I won’t include a full listing – the code has simply grown too large.However, I will go over all the pertinent parts so you can learn the most from this tutorial. Anything that waschanged or modified will be shown.

Going from 2D to 3D

The code in the last tutorial drew a simple 2D triangle on the screen. Even though we specified a z-coordinatefor each vertex of that triangle, it was still not really 3D. Changing the z-coordinate for any of the vertices stilldidn’t draw the triangle in perspective. Why is this?

The vertex type used was D3DFVF_XYZRHW – the type used for pre-transformed vertices. DirectX expectsthese vertex types to have already gone through a 3D transformation pipeline. Once through the pipeline,z-coordinates become irrelevant – only screen coordinates (x and y) are used.

In order to render a true 3D scene, we need to add and/or change two things:

Feed the following matrices to DirectX so it knows how to transform the vertices from world space tothe screen: world transformation matrix, view matrix, and projection matrix.Change the vertex types to D3DFVF_XYZ – these vertices are untransformed and relative to worldspace.

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

6 of 26 10/7/2010 11:01 AM

Page 7: C++ Graphics Directx

Adding the Transformation Matrices

Before getting into the code, let’s briefly go over the purpose of each matrix. As the last section mentioned,DirectX needs the following three matrices:

The world transformation matrix.The view transformation matrix.The projection transformation matrix.

The world transformation matrix tells DirectX how to rotate, translate, and possibly scale 3D modelcoordinates into world space. In a typical 3D video game, polygonal models are the objects which tend to bereused in different parts of the world (weapons, bonus items, enemy players, monsters, etc.). Their verticesare defined relative to their own local coordinate system. The world transformation matrix converts theselocal coordinates into absolute positions in the 3D world (hence the name).

The view matrix (sometimes called the camera matrix, or camera-view matrix) tells DirectX how totransform world coordinates into camera coordinates (basically, where are you and what are you looking at).The world coordinates become relative to the camera axes after this matrix is applied.

If you have some experience in 3D graphics programming, don’t confuse the world matrix with the viewmatrix. Many tutorials and books about graphics sometimes refer to the world matrix and view matrix as oneof the same. This is due to a certain optimization that can be done where you combine the world matrix andthe view matrix into one master transformation matrix resulting in just one matrix update per frame instead oftwo.

The projection matrix tells DirectX about your 2D viewport into the 3D world. It holds the followinginformation about the screen and camera: field of view, aspect ratio of the screen, how far the camera can see(far clipping plane), and how near the camera can see (near clipping plane).

The process of creating a world, view, and projection matrix isn’t difficult – if you use the Direct3DExtensions Utility library. Among other things, this library contains a few useful functions that return fullypopulated matrices given certain parameters. For example, provide an angle for the D3DXMatrixRotationY()function, and it will return a world transformation matrix that does rotation around the y-axis. If you everneed to calculate these matrices yourself, without the library’s help, you can refer to DirectX’s SDKdocumentation – it contains the layout and formulas for each matrix.

The order in which you feed these matrices to DirectX is irrelevant – although it internally applies them to thescene in the same order (world -> view -> projection). Since the order doesn’t matter, we set the projectionmatrix at initialization time and then forget about it. This matrix would only need to change if the screen’saspect ratio, field of view, or clipping planes were altered.

We add the following code in order to create and set the projection matrix:

D3DXMATRIXA16 ProjectionMatrix;

D3DXMatrixPerspectiveFovLH(&ProjectionMatrix, PI/4, 1.0f, 1.0f, 500.0f);

g_pDirect3D_Device->SetTransform(D3DTS_PROJECTION, &ProjectionMatrix);

The call to D3DXMatrixPerspectiveFovLH() creates a projection matrix given the following values:

Field of view (in radians).Aspect ratio.

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

7 of 26 10/7/2010 11:01 AM

Page 8: C++ Graphics Directx

Z-value of the near clip plane.Z-value of the far clip plane.

These values go in parameters two through five, respectively. The first parameter holds a pointer to the matrixobject which will receive the result.

The last line calls SetTransform(). This function is used to feed DirectX all the different types of matrices. Thefirst parameter distinguishes which matrix type you want to set. D3DTS_PROJECTION indicates a projectionmatrix is contained in the second parameter.

Next we create the world transformation matrix. This is also set at initialization time. Why? Our exampleprogram has no 3D polygonal models and therefore doesn’t need to use it. As such, we simply send DirectXan identity matrix so it doesn’t affect any of the math in the 3D pipeline. Here’s what that code looks like:

D3DXMATRIXA16 WorldTransformMatrix;

D3DXMatrixIdentity(&WorldTransformMatrix);

g_pDirect3D_Device->SetTransform(D3DTS_WORLD, &WorldTransformMatrix);

We initialize the world transform matrix to an identity with the D3DXMatrixIdentity() function and then callSetTransform() just as we did with the projection matrix. The first parameter, D3DTS_WORLD, tells DirectXto use this matrix as the world transform. One thing to note: instead of calling D3DXMatrixIdentity(), wecould have easily set the matrix manually through the object’s constructor:

D3DXMATRIXA16 WorldTransformMatrix(1, 0, 0, 0,

0, 1, 0, 0,

0, 0, 1, 0,

0, 0, 0, 1);

I used the function call instead for clarity, but both methods are equivalent.

Now for the view/camera matrix. This one we must set on every frame since the direction of the camera canchange at any time. If you take a look at the code, you’ll notice a function named CalcMatrices() – this iswhere the matrix is being populated. That code looks like:

void CalcMatrices(void)

{

D3DXMATRIXA16 ViewMatrix;

// set the view matrix

D3DXVECTOR3 EyePoint(g_Camera.Location.x,

g_Camera.Location.y,

g_Camera.Location.z);

D3DXVECTOR3 LookAt(g_Camera.Location.x+cos(g_Camera.Rotation),

g_Camera.Location.y,

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

8 of 26 10/7/2010 11:01 AM

Page 9: C++ Graphics Directx

g_Camera.Location.z+sin(g_Camera.Rotation));

D3DXVECTOR3 UpVector(0.0f, 1.0f, 0.0f);

D3DXMatrixLookAtLH(&ViewMatrix, &EyePoint, &LookAt, &UpVector);

g_pDirect3D_Device->SetTransform(D3DTS_VIEW, &ViewMatrix);

}

In this function we must give DirectX two points and something called an “up” vector. The first point is thecamera’s position – its location in 3D space. The second is any point along the camera’s direct line-of-sight –in other words, any point that, if you were to look through the camera, would be centered in the camera’sfield of view. DirectX simply uses these two points to calculate the camera’s view vector – what direction thecamera is pointing. Since the look-at point can be any point along the camera’s line-of-sight, I just use the sineand cosine functions to calculate some point directly in front of the camera. The y-coordinate doesn’t changebecause, in this example program, I’ve tried to keep things simple and not allowed the camera to look up ordown (only side-to-side). The “up” vector defines which direction points directly up from the camera’s pointof view.

You may be wondering why the up-vector is needed if we already have two points describing the camera’sdirection. Here’s why: suppose that the camera is looking straight ahead, directly down the z-axis. Now turnthe camera up-side down (in other words, rotate it 180 degrees around the z-axis). Did the camera’s viewingdirection change? Nope. What if the camera was turned side-ways (or rotated 90 degrees around the z-axis)?Even then, the viewing direction doesn’t change (it’s still looking down the z-axis). So using just two pointsgives enough information to know where the camera is pointing, but it doesn’t describe the “roll” of thecamera relative to itself. In this tutorial I haven’t allowed the camera to roll over, so the up-vector stays at (0,1, 0) – in other words, the camera can’t look up or down.

Once we’ve created the camera point, look-at point, and up-vector, we pass all of them toD3DXMatrixLookAtLH() – a function that calculates a view/camera matrix and puts it into the firstparameter, ViewMatrix.

Finally we call SetTransform() to feed DirectX our newly calculated matrix. The first parameter,D3DTS_VIEW, tells DirectX to use this matrix as the view/camera matrix. The second parameter is a pointerto the matrix itself.

The Rendering Loop

Now that all the matrices have been set, we’re ready to tackle the main rendering loop. Just as in the lasttutorial, we begin by setting up the vertex format structure:

struct D3DVERTEX {float x, y, z; DWORD color;} vertices[NUM_VERTICES];

This time we don’t need the “rhw” component. We’re feeding DirectX non-transformed vertices so thereforea “w” component doesn’t apply. DirectX just needs the (x, y, z) components and the color of each vertex.

We then fill in the “vertices” array. Here’s an example of the first point:

vertices[0].x = -64.0f*3;

vertices[0].y = -64.0f;

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

9 of 26 10/7/2010 11:01 AM

Page 10: C++ Graphics Directx

vertices[0].z = 0;

vertices[0].color = FRONT_WALL_COLOR;

<…more vertices here…>

Once we’ve filled in all the vertices, we must feed them to DirectX. The code which feeds DirectX is exactlythe same as before (see last tutorial) with one exception – the vertex format is different.

LPDIRECT3DVERTEXBUFFER9 pVertexObject = NULL;

void *pVertexBuffer = NULL;

if(FAILED(

g_pDirect3D_Device->CreateVertexBuffer(NUM_VERTICES*sizeof(D3DVERTEX), 0,

D3DFVF_XYZ|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pVertexObject, NULL)))

return;

if(FAILED(pVertexObject->Lock(0, NUM_VERTICES*sizeof(D3DVERTEX),&pVertexBuffer, 0)))

return;

memcpy(pVertexBuffer, vertices, NUM_VERTICES*sizeof(D3DVERTEX));

pVertexObject->Unlock();

The vertex type, D3DFVF_XYZRHW, gets replaced with D3DFVF_XYZ because we’re feeding in (x, y, z)components instead of (x, y, z, 1/w). I’m not going to go into the inner-workings on this code because it wasalready covered in the last tutorial.

Now we’re ready to render the scene. Again, this code looks much like the last tutorial except the vertexformat has changed, plus, we’re now calculating the transformation matrices before rendering.

g_pDirect3D_Device->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); // clear frame

if(SUCCEEDED(g_pDirect3D_Device->BeginScene()))

{

CalcMatrices();

g_pDirect3D_Device->SetStreamSource(0, pVertexObject, 0,

sizeof(D3DVERTEX));

g_pDirect3D_Device->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE);

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

10 of 26 10/7/2010 11:01 AM

Page 11: C++ Graphics Directx

g_pDirect3D_Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, NUM_VERTICES/3);

g_pDirect3D_Device->EndScene();

}

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

pVertexObject->Release();

This time around, when we call SetFVF(), we supply the (x, y, z) vertex format instead of (x, y, z, 1/w) usingthe D3DFVF_XYZ constant as opposed to D3DFVF_XYZRHW. Also, we call CalcMatrices() before drawingany primitives so DirectX knows which transformations to apply to the scene. The rest of the code behavesexactly the same as the last tutorial, so I’m not going to cover it again.

Handling Keyboard Input

The tutorial program uses DirectX/DirectInput to capture keyboard actions, but I’m not going to cover howDirectInput works here. Instead, I’m going to show how the program reacts to different key presses.

The function HandleKeys() is called on every frame and is responsible for updating the global camera positiontracking variables depending on the keyboard state.

void HandleKeys(void)

{

float RotationStep = PI/175.0f;

float WalkStep = 3.0f;

//——————————————-

// adjust the camera position and orientation

//——————————————-

if(dx_keyboard_state[DIK_UP]&0×80) // moving forward

{

g_Camera.Location.x += cos(g_Camera.Rotation)*WalkStep;

g_Camera.Location.z += sin(g_Camera.Rotation)*WalkStep;

}

if(dx_keyboard_state[DIK_DOWN]&0×80) // moving backward

{

g_Camera.Location.x -= cos(g_Camera.Rotation)*WalkStep;

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

11 of 26 10/7/2010 11:01 AM

Page 12: C++ Graphics Directx

g_Camera.Location.z -= sin(g_Camera.Rotation)*WalkStep;

}

if(dx_keyboard_state[DIK_LEFT]&0×80) // look left

{

g_Camera.Rotation += RotationStep;

if(g_Camera.Rotation > PI*2)

g_Camera.Rotation = g_Camera.Rotation-PI*2;

}

if(dx_keyboard_state[DIK_RIGHT]&0×80) // look right

{

g_Camera.Rotation -= RotationStep;

if(g_Camera.Rotation < 0)

g_Camera.Rotation = PI*2+g_Camera.Rotation;

}

if(dx_keyboard_state[DIK_W]&0×80) // strafe left

{

float SideStepAngle = g_Camera.Rotation+(PI/2.0f);

if(SideStepAngle > PI*2) // handle wrap-around

SideStepAngle = SideStepAngle-PI*2;

g_Camera.Location.x += cos(SideStepAngle)*WalkStep;

g_Camera.Location.z += sin(SideStepAngle)*WalkStep;

}

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

12 of 26 10/7/2010 11:01 AM

Page 13: C++ Graphics Directx

if(dx_keyboard_state[DIK_E]&0×80) // strafe right

{

float SideStepAngle = g_Camera.Rotation-(PI/2.0f);

if(SideStepAngle < 0) // handle wrap-around

SideStepAngle = PI*2+SideStepAngle;

g_Camera.Location.x += cos(SideStepAngle)*WalkStep;

g_Camera.Location.z += sin(SideStepAngle)*WalkStep;

}

}

Walking forward and backward is a simple matter of updating the camera’s (x, z) position. We don’t updatethe y-coordinate because there’s no way to “jump” or float in the air for this example. If we were using apolar coordinate system (where point positions are based on angle and ray length), moving the cameraforward or backward would be easy – just increase or decrease the ray length. But since we’re in therectangular coordinate system (where point positions are determined by x, y, and z), we must convert thisincrease or decrease of ray length into the (x, z) equivalent. We do this with the sine/cosine functions andthen add the result to the camera’s last position in order to get the new position (or subtract from the camera’slast position if we’re moving backwards). I’m not going to get into the basics of simple trigonometry, but ifyou want a detailed explanation of how these trajectory formulas work, email me through the Contact page.

Strafing, or side-stepping, is done just like moving forward or backwards except the angle used in thecalculation is 90 degrees plus or minus the camera’s real angle. If you’re moving left the angle is plus 90, andif you’re moving backwards, the angle is minus 90.

For looking left or right we just add or subtract from the camera’s current angle to get the new angle.However, we must check to make sure the angle hasn’t “overflowed” or gone below zero. After all, a circlehas a maximum of 360 degrees – so rotating by 370 degrees is really the same as rotating by just 10 degrees.Same goes for the other side – rotating by negative 10 gets you to the same point as +350.

All this updating of the camera’s global position/orientation object is eventually used by the CalcMatrices()function in order to create a new view matrix on every frame. In other words, DirectX always has the mostcurrent camera position and renders the room based on the camera position controlled by the keyboard.

Screenshot

Here’s what the output looks like:

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

13 of 26 10/7/2010 11:01 AM

Page 14: C++ Graphics Directx

Conclusion

Wow, this post ended up being _way_ longer than expected! Anyway, if you have any questions about any ofthe material covered, please post a comment or send me an email through my Contact page.

Thanks for reading!

-Greg Dolley

Posted in 3D Graphics, 3D Mathematics, C++ Graphics, DirectX | 36 Comments »

DirectX 9 C++ Graphics Tutorial 2: Drawing a Triangle

Posted by gregd1024 on February 28, 2008

In this tutorial we’re going to look at how to draw a stationary triangle using DirectX 9 and C++. We’ll bebuilding off of the concepts taught in the first tutorial (DirectX 9 C++ Graphics Tutorial 1: Getting Started).Most of the code will be the same since it involves setting up a form and initializing DirectX.

Requirements

The tutorial sample code was built and tested with Visual Studio Express 2008. However, using the code“as-is” in Visual Studio 2005 should work too.

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

14 of 26 10/7/2010 11:01 AM

Page 15: C++ Graphics Directx

You’ll need the DirectX 9 SDK for compiling the sample code. Use this link for downloading: click here.

Tutorial Source and Project Files

Download the project files, binaries, and source with this link:

Download C++ Sources and Binaries [~12k]

Getting Started

I’m not going to cover the Win32 initialization code or how to initialize DirectX in this tutorial. For that, seethe previous tutorial: DirectX 9 C++ Graphics Tutorial 1: Getting Started.

The only difference between this tutorial’s code versus the last version is how we handle WM_PAINTmessages. Previously, we simply cleared the color buffer and displayed it on the screen. This time we’re goingto define three triangle vertices, create a vertex buffer, send that vertex buffer to DirectX, and finally tellDirectX to render the triangle.

Defining the Vertices

In DirectX there is no pre-defined vertex type or object. You have to make your own struct (or class) andthen tell DirectX about its format via FVF (Flexible Vertex Format) codes. FVF codes are a set of constantsthat describe the contents and size of a vertex structure. For example, the constant D3DFVF_XYZ describesyour structure as having three float variables representing an untransformed vertex; the constantD3DFVF_DIFFUSE describes a single DWORD value representing a diffuse color component in ARGBorder. You can (and often will) combine a set of FVF code together. For instance,“D3DFVF_XYZ|D3DFVF_DIFFUSE” means your structure has three float variables followed by oneDWORD variable. The correlation between constant and vertex layout is clearly defined in the DirectX SDKdocumentation.

In our sample program we used the following structure:

struct D3DVERTEX{ float x, y, z, rhw; DWORD color;};

The (x, y, z, rhw) combination describe the transformed position of a vertex. The “color” member describesits diffuse color in the format of ARGB (Alpha, Red, Green, Blue). To describe this structure we use thefollowing FVF code:

“D3DFVF_XYZRHW|D3DFVF_DIFFUSE” – transformed position with color info.

Let’s look at the code for handling the WM_PAINT event where it actually creates the vertices:

case WM_PAINT:

// setup vertex information

struct D3DVERTEX {float x, y, z, rhw; DWORD color;} vertices[3];

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

15 of 26 10/7/2010 11:01 AM

Page 16: C++ Graphics Directx

vertices[0].x = 50; vertices[0].y = 50; vertices[0].z = 0; vertices[0].rhw = 1.0f; vertices[0].color = 0x00ff00;

vertices[1].x = 250; vertices[1].y = 50; vertices[1].z = 0; vertices[1].rhw = 1.0f; vertices[1].color = 0x0000ff;

vertices[2].x = 50; vertices[2].y = 250; vertices[2].z = 0; vertices[2].rhw = 1.0f; vertices[2].color = 0xff0000;

In this code we really just define a vertex array (“vertices[3]“) and fill in the values for a triangle.

Creating the Vertex Buffer

Next, we tell DirectX about our vertex data by creating a vertex buffer object. Here’s the code to do it (thiscode comes directly after the code in the last section):

LPDIRECT3DVERTEXBUFFER9 pVertexObject = NULL;void *pVertexBuffer = NULL;

if(FAILED(g_pDirect3D_Device->CreateVertexBuffer(3*sizeof(D3DVERTEX), 0, D3DFVF_XYZRHW|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pVertexObject,NULL))) return(0);

if(FAILED(pVertexObject->Lock(0, 3*sizeof(D3DVERTEX), &pVertexBuffer, 0))) return(0);

memcpy(pVertexBuffer, vertices, 3*sizeof(D3DVERTEX));pVertexObject->Unlock();

The first two lines just declare the pointers we’re going to use. The next line calls a DirectX function calledCreateVertexBuffer(). This function allocates a vertex buffer object which we’ll use for all buffer operations.

CreateVertexBuffer() takes six parameters. The first parameter tells DirectX the required size of the vertexbuffer (in bytes). The second parameter specifies how the vertex buffer will be used – “0″ being the default.The third parameter tells DirectX about the memory layout of each vertex (the FVF format). The fourthparameter says that you don’t care where memory is allocated. The fifth parameter is the address of a pointerto be filled with the vertex buffer object location. Lastly, the sixth parameter specifies a shared handle (don’tworry about this).

Now we use our newly created vertex buffer object and call its Lock() method. This call gives us a memory

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

16 of 26 10/7/2010 11:01 AM

Page 17: C++ Graphics Directx

buffer (pointed to by pVertexBuffer) that we must copy our vertex data into. I know this seems strange – wealready created our own vertex array, filled it with data, now we have to copy it somewhere else? Don’t askwhy, this is just how DirectX works. The next line, with the call to memcpy(), does this copying process.

Finally, when we’re done with the copy, we have to tell DirectX that the data is ready to go. This is done viathe Unlock() method of the vertex buffer object.

Rendering the Vertex Buffer

Now we’re ready to actually draw the scene! Check out the following code (again, this code comes directlyafter the last line of the previous section):

// clear background to blackg_pDirect3D_Device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0),1.0f, 0);

// render the actual sceneif(SUCCEEDED(g_pDirect3D_Device->BeginScene())){ g_pDirect3D_Device->SetStreamSource(0, pVertexObject, 0,sizeof(D3DVERTEX)); g_pDirect3D_Device->SetFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE); g_pDirect3D_Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); g_pDirect3D_Device->EndScene();}

g_pDirect3D_Device->Present(NULL, NULL, NULL, NULL);pVertexObject->Release();

ValidateRect(hwnd, NULL);

First, we clear the background to black by calling Clear(). You might recognize this from the previous tutorial(it’s the exact same call, but the color is different).

Next we call BeginScene() – every 3D frame in DirectX begins with this call.

The next two lines set the stream source and vertex format. SetStreamSource() tells DirectX to take thevertex information from our vertex buffer object. The first parameter specifies the stream number and thesecond is a pointer to the vertex buffer object itself. The third parameter says there is no offset from thebeginning of the stream to the vertex data (if there was, this value would be the number of bytes in between).Finally, the fourth parameter is the size, in bytes, of each vertex. SetFVF() sets the FVF code that describesour vertex format (see “D3DFVF” in the DirectX SDK documentation for all the possible code combinationsand their corresponding formats).

Now we’re at DrawPrimitive(). This tells DirectX to actually draw something. The first parameter specifieswhat to draw – in our case, a list of triangles (actually, just one triangle since our list only contains oneelement). The second parameter says start from vertex zero (in some cases you may want to start fromsomewhere other than the beginning). And the last parameter tells DirectX how many primitives to draw (forus, just one triangle).

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

17 of 26 10/7/2010 11:01 AM

Page 18: C++ Graphics Directx

Once all the drawing code is executed, we must call EndScene(). All 3D scenes in DirectX end with this call.

Finally we call Present() to display everything on the screen and call Release() on our vertex buffer object(since it’s no longer needed).

ValidateRect() is from the first tutorial. It tells Windows that we’ve handled all of the drawing in this windowand Win32 doesn’t need to do any further processing.

Program Output

All this code generates the following output:

Notice how the colors of each vertex are “smoothed” over the face of the triangle. This is the defaultbehavior of DirectX (and even OpenGL). Since colors are only defined for the vertices and not the face of thepolygon, DirectX interpolates the color in between. This interpolation gives you the gradient effect.

Conclusion

Now that’s definitely better than the last tutorial of simply drawing a solid color on a form. However, thetriangle is technically still 2D. If it were a 3D triangle we could adjust the z coordinate at each vertex, givingthem different depths, and watch the triangle get drawn in perspective. Currently that’s not possible with thiscode. In order to turn it into a “real” 3D triangle, we need a camera position, a transformation matrix, and acouple other components. We’ll be discussing all this in the next DirectX C++ tutorial.

However, for the very next post, I’ll go over the Managed DirectX version of this same stuff using C#.

-Greg Dolley

*Get new posts automatically! Grab the RSS feed here. Want email updates instead? Click here.

Posted in 3D Graphics, C++ Graphics, DirectX | 33 Comments »

DirectX 9 C++ Graphics Tutorial 1: Getting Started

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

18 of 26 10/7/2010 11:01 AM

Page 19: C++ Graphics Directx

Posted by gregd1024 on February 20, 2008

In this post I’m going to cover writing the most basic DirectX 9 application in native C++. You’ll see how topaint a solid color on a form as well as the minimum amount of code all DirectX 9 applications must have.Note: when I refer to DirectX, unless otherwise specified, I’m strictly referring to the graphics portion of it –Direct3D.

This post is going to be one of a series. I’m going to cover writing simple DirectX 9 applications in C++ andI’ll also cover how to write those same applications with Managed DirectX (MDX 1.1) in C#. These postswill alternate – one in C++, one in C#, back to C++, and so on. After this, I’ll eventually cover some DirectX10 stuff in C++ (which can be vastly different from DirectX 9 in certain areas). There is no MDX equivalentof the C++ DX10 API – the XNA Framework is meant to replace MDX since Microsoft dropped support forthe MDX 2.0 Beta project. However, I’m not sure if XNA is mature enough yet to be able to do all of thingsoriginally included in MDX 2.0. I’ll explore that issue in a future post.

Requirements

The steps and screenshots in this tutorial are based on Visual Studio 2008 Express. However, everything wasalso tested in Visual Studio 2005 Professional.

You’ll need the DirectX SDK to run the sample programs. You can download it from Microsoft’s site with thislink. Note, however, that their latest release (which is November 2007 at the time of this writing) took out theMDX 1.1 samples and documentation. If you still want those, download the August 2007 release or earlier (Iuse the June release).

Tutorial Source and Project Files

To get the finished result of what this tutorial teaches – project files, binaries, and source – use this link:

Download C++ Sources and Binaries [~12k]

If you follow this tutorial’s instructions exactly as specified, you should end up with the same code andoutput. However, copying and pasting from a web page to Visual Studio sometimes results in double spacesbetween lines where it wasn’t intended. In almost all cases C++ doesn’t care and your code will still compileperfectly fine (a multi-line macro is one of those rare exceptions).

Setting Up the Project

Start out by performing the following steps:

Create an empty project and add one source file called “main.cpp” (you can actually name it anything youwant, but I’m going to refer to this file as “main.cpp” throughout the text).

Go into the Project Properties dialog and add the following include directory, “$(DXSDK_DIR)\Include”:

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

19 of 26 10/7/2010 11:01 AM

Page 20: C++ Graphics Directx

Add the following preprocessor directives, “WIN32;_DEBUG;_WINDOWS”:

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

20 of 26 10/7/2010 11:01 AM

Page 21: C++ Graphics Directx

Add the following library dependency, “d3d9.lib”:

All the other defaults are fine. Save these settings and move on to the next step.

Creating the Form

Drop the following code snippet into your main.cpp file. This is the smallest amount of code that all C++Windows form applications must have:

#include <windows.h>

LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine,

int nShow){ MSG msg;

WNDCLASSEX wc = {sizeof(WNDCLASSEX), CS_VREDRAW|CS_HREDRAW|CS_OWNDC, WndProc, 0, 0, hInstance, NULL, NULL, (HBRUSH)(COLOR_WINDOW+1), NULL, “DX9_TUTORIAL1_CLASS”, NULL};

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

21 of 26 10/7/2010 11:01 AM

Page 22: C++ Graphics Directx

RegisterClassEx(&wc);

HWND hMainWnd = CreateWindow(“DX9_TUTORIAL1_CLASS”,

“DirectX 9 Bare Bones Tutorial 1″, WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,

NULL, NULL, hInstance, NULL); ShowWindow(hMainWnd, nShow); UpdateWindow(hMainWnd);

while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }

return(0);}

LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){ switch(msg) { case WM_DESTROY: PostQuitMessage(0); return(0); }

return(DefWindowProc(hwnd, msg, wParam, lParam));}

Since the purpose of this tutorial is to go over DirectX and not basic Win32 programming, I’m going to keepthe following description of the code brief. There are many Win32 tutorials on the internet that explain inextreme detail the low-level aspects of Win32.

First we must define a WinMain() function and include <windows.h>. The windows.h header file includesmost of the definitions needed to access the Win32 API. In order to run any Win32 program, the followingthree steps must be performed:

Create a windows class.1.Create a window object.2.Show the window.3.

In addition to these steps, all Windows programs must have something called a “message pump.” This is themechanism in which the Win32 subsystem sends your application events about what is happening on theoutside (i.e., what the user is doing in your app, global events of the operating system – such as starting toreboot, etc.). Your application must either respond to these events or must pass them to the default Win32event handler.

For the first step, create a Windows class – this doesn’t have anything to do with a C++ class. It is a Win32specific term that tells Windows about your program. It needs some minimal information in order to setupyour application to run in the Windows environment. It needs to know:

How GDI should handle your application’s main window.1.

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

22 of 26 10/7/2010 11:01 AM

Page 23: C++ Graphics Directx

A function pointer to your program’s message handler.2.The instance handle of your program.3.A background brush (this represents the background color, but technically it’s not required – yourwindow will appear transparent if omitted).

4.

Class string (a unique string that identifies your Win32 class object to the rest of Windows).5.

The WNDCLASSEX structure is used to hold this information. While there are more than five members inWNDCLASSEX, the code above specifies NULL or “0″ for the properties in which Windows will take thedefault. The RegisterClassEx() function is used to send this information to the Win32 subsystem.

The next step is to create the actual window object. This object turns into the application’s main form. TheCreateWindow() function, like its name implies, is used to create a window object. This function needs atleast the following information:

The same class string used when calling RegisterClassEx().1.The title of the form.2.The style of the form (dialog, tool window, etc.).3.The (x, y) position of the form and its size.4.The instance handle of your program (this should match the instance handle used when creating theclass object).

5.

As long as no error is generated, the CreateWindow() function returns a handle to your window. NULL willbe returned if an error occurred. The next step is take that window handle and call ShowWindow(). The firstparameter specifies the window handle and the second parameter represents how the window is to be shownby default. We used the “nShow” variable as the second parameter – the forth argument to WinMain(). Thisallows Windows to tell us how the window should be shown based on how the application was launched. Thenext line calls UpdateWindow(), which is technically not required by a program this simple. It allows anypost-updates to occur after the window is first drawn.

We now get to the while() loop at the end of the main function. This loop is part of the “message pump”discussed earlier. If this loop wasn’t there your application would simply start and stop right away. Thereneeds to be something that keeps your program running until it’s time to close. The “message pump” loopdoes exactly this. It continually checks for events from the Win32 subsystem by calling GetMessage() andsubsequently passes them down to TranslateMessage() and DispatchMessage(). These two functionseventually route the events to WndProc() – your callback function specified in the windows class object. Thisfunction allows your program to execute whatever code is necessary in response to these events. If yourapplication doesn’t need to respond to an event, it should pass it down to the default Win32 handler by callingDefWindowProc().

Creating the DirectX Object and Device

Now we must create two objects: the main DirectX object that allows us to interface with the API and the“device” object which allows interaction with the 3D hardware. Begin doing this, you’ll need to include theDirect3D header file and declare a couple global pointers to the DirectX object and device object:

#include <d3d9.h>

// globalsLPDIRECT3D9 g_pDirect3D = NULL;LPDIRECT3DDEVICE9 g_pDirect3D_Device = NULL;

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

23 of 26 10/7/2010 11:01 AM

Page 24: C++ Graphics Directx

In order to create the DirectX object and device object, we add the following code directly under the call toCreateWindow() right above ShowWindow():

g_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);

D3DPRESENT_PARAMETERS PresentParams;memset(&PresentParams, 0, sizeof(D3DPRESENT_PARAMETERS));

PresentParams.Windowed = TRUE;PresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;

g_pDirect3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hMainWnd,

D3DCREATE_SOFTWARE_VERTEXPROCESSING, &PresentParams,

&g_pDirect3D_Device);

Let’s go through this line by line. First we create the DirectX object by calling Direct3DCreate9(). The firstparameter, D3D_SDK_VERSION, is a special number that ensures an application was built against thecorrect header files. If the number doesn’t match, the call will fail.

Next we declare and fill in a D3DPRESENT_PARAMETERS structure. This object holds properties abouthow an application should behave. In our sample, we want it to run in windowed mode and we want the videohardware to handle the back buffer instead of handling it ourselves (the full meaning ofD3DSWAPEFFECT_DISCARD is beyond the scope of this tutorial, but basically it tells the device driver toselect the most efficient way of handling swap chains).

We then use the main DirectX object to call CreateDevice(). This function does the work of allocating thedevice object and associating that object with the 3D hardware (or software – depending on what parametersare specified). The first parameter, D3DADAPTER_DEFAULT, tells DirectX to take whatever graphics cardis the default (typically the one and only piece of active video hardware connected to your motherboard). Thesecond parameter, D3DDEVTYPE_HAL, tells DirectX to use 3D hardware, instead of a software emulator,for rendering. A software emulator becomes useful only for low-level debugging – don’t worry about it fornow. The third parameter specifies the window handle that will receive the rendered output.D3DCREATE_SOFTWARE_VERTEXPROCESSING tells DirectX to use software for transformationcalculations instead of hardware HTL (Hardware Transform & Lighting). If you had specified hardwaretransformation calculations instead, and the video card didn’t support HTL, then this function would fail. Allnew cards on the market today support HTL, and most have for quite a while. So unless your card is really oldyou can select hardware processing without any worries – just replace the existing constant withD3DCREATE_HARDWARE_VERTEXPROCESSING. The fifth parameter is a pointer to theD3DPRESENT_PARAMETERS object that we filled in right above the call. Lastly, the sixth parameter holdsa pointer to the device object that will be created and returned by the function.

Now that the device object is created, we can call its functions to control the video card! The next section willexplain exactly how to do this.

Draw on the Window

In this section we’ll see where the magic happens. While it’s not much – drawing a solid color in the window– you’ll see how to do more advanced stuff in the next few tutorials.

Before we begin, we must first handle the WM_PAINT event inside our WndProc() message handlerfunction. The WM_PAINT event is fired whenever the interior of a window needs its contents redrawn. Wecapture the event by enumerating the WM_PAINT constant inside WndProc’s switch statement:

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

24 of 26 10/7/2010 11:01 AM

Page 25: C++ Graphics Directx

switch(msg){ case WM_DESTROY: PostQuitMessage(0); return(0); case WM_PAINT: // <— ADD THIS BLOCK // drawing code goes here… ValidateRect(hwnd, NULL); return(0);}

ValidateRect() is called in order to tell Win32 that we’ve handled all of the rendering ourselves.

Now for the fun part – filling in the drawing code. Add the following two lines directly below the “caseWM_PAINT” statement (where it says, “drawing code goes here…”):

g_pDirect3D_Device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255),

1.0f, 0);g_pDirect3D_Device->Present(NULL, NULL, NULL, NULL);

The first call to Clear() tells DirectX to fill a specific rectangle, or set of rectangles, with a certain color. Thefirst two parameters represent the number of rectangles and the address of where those rectangles are stored.If “0″ and “NULL” are specified instead, DirectX will fill the entire rendering target. The third parameter,D3DCLEAR_TARGET, means we want to clear the actual pixel buffer as opposed to the depth buffer orstencil buffer. This parameter could also be used to clear a combination of those buffers at the same time. Thelast three parameters tell DirectX what values to use for clearing the pixel buffer, depth buffer, and stencilbuffer. Since we’re not clearing the depth buffer or stencil buffer, the values for the last two parameters haveno effect.

Once everything has been rendered to the back buffer you display it on the screen by calling Present().Parameters one and two represent the source and destination rectangles, respectively. Since we want theentire back buffer to get copied to the entire screen, we pass in NULL. The third parameter specifies whichwindow to use for the display. When this parameter is NULL, Present() looks at what was originally set whencreating the D3DPRESENT_PARAMETERS object (specifically the hDeviceWindow member). Since wedidn’t specify anything in the beginning of our program, the memset() call would have set this member toNULL. When this happens, and windowed mode is set, Present() takes the focus window as its destination.

Shutting Down

We allocated two DirectX objects in our program – the main interface object and the device object. Theseobjects must eventually be freed. Since we aren’t doing anything fancy that requires destruction andreallocation of these objects, we can simply put the clean up code right above the “return” statement inWinMain():

g_pDirect3D_Device->Release();g_pDirect3D->Release();

We call Release() because these objects were given to us by DirectX via COM as opposed to being allocatedwith the “new” keyword or created with some memory allocation function. Therefore we must delete themthrough COM’s Release() function.

Run the Program!

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

25 of 26 10/7/2010 11:01 AM

Page 26: C++ Graphics Directx

You can now compile and run the program. Here’s what the output looks like:

OK, it’s not much at this point, but in the next few tutorials we’ll get into more exciting things.

Conclusion

OK, I admit, the final output of this tutorial wasn’t too exciting, but it only strived to cover the absoluteminimum – nothing more, nothing less. All 3D DirectX applications start off this way. If someone made aDirectX project template for Visual Studio, the wizard would more than likely generate very similar code towhat you see here.

In the next tutorial, we’ll see how to do the same thing in managed DirectX (MDX 1.1).

-Greg Dolley

*Get new posts automatically! Grab the RSS feed here. Want email updates instead? Click here.

Posted in C++ Graphics, DirectX | 61 Comments »

Blog at WordPress.com. | Theme: Andreas09 by Andreas Viklund.

C++ Graphics « Greg Dolley’s Weblog http://gregs-blog.com/category/c-graphics/

26 of 26 10/7/2010 11:01 AM