The Ultimate DirectX Tutorial
Lesson 6: Rendering Depth
Lesson Overview

Using the pipeline presents us with one little problem. While it flawlessly calculates the screen location of 3D vertices, it does not show depth. You will see exactly what I mean by this in a minute. The problem is that Direct3D draws over all images when drawing (whether or not that image was closer or not). So far, we have only worked with one triangle, and so you haven't had a chance to see this in action.

This lesson will go over the anatomy of this problem, and a good way to fix it.

Understanding the Problem At Hand

Let's say we wanted to draw two triangles, one behind the other, and then view them from an angle where the farther triangle was partially behind the other. If we did this with what code we've covered so far, this is how that might look:

Defying the Laws of Physics

This, unfortunately, defies the laws of physics. Things that are farther do not usually appear in front of closer things, especially when the closer thing is blocking it. The way it should appear is like this:

Obeying the Law

How This Works (or rather, why it doesn't)

When a model is rendered, several things happen. First, Direct3D calls up the pipeline you built. It is all neatly packed away in memory. Direct3D takes this and processes each model, one at a time, into a 2D image. Immediately after creating that image, it is drawn to the back buffer.

After the first image has been drawn to the screen, the next model is taken up, processed, and drawn to the back buffer. However, no matter where the model was placed in 3D space, the second image is shown over the first one, and you get the result shown in the first image.

Fortunately, Direct3D provides an easy solution to this. The solution is known as a Z-Buffer.

Z-Buffers

A Z-Buffer, also known as a depth buffer, is simply a large buffer that keeps track of the distance from the camera of every pixel on the screen. This is illustrated in the following image.

The Z-Buffer (Or Depth Buffer)

This diagram shows how a z-buffer works. Whenever a pixel is drawn, it takes the closest pixel to the camera and draws that on the back buffer. At the same time, it stores the depth value into the same spot in the z-buffer, so that the next time something is drawn, Direct3D can see how close each pixel is, and which objects should be drawn and which should not.

Now that you understand the concept of a z-buffer, let's go over how to implement the z-buffer into your game.

Including the Z-Buffer

I'm going to dive right in. There are three key steps to Z-Buffering.

1. Setting the Appropriate Presentation Parameters
2. Turning On Z-Buffering
3. Clearing the Z-Buffer

Each of these steps are very simple. Let's go over them now.

1. Setting the Appropriate Presentation Parameters

This first step takes us all the way back to the first few lines of Direct3D we run. We only add two lines of code to this, and make no further changes. I'll show you the changes, then explain what they do. As usual, changes are in bold.

D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = FALSE;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

Let's go over what these do.

EnableAutoDepthStencil

In truth, z-buffering can be complex. Setting this value to TRUE tells Direct3D to automatically create the z-buffer and set it up in a way used most often. There are, of course, uses for the complex method, but we'll stick to simple for now. We'll cover ways the complex method can be useful later in the tutorial.

AutoDepthStencilFormat

This is the format for each pixel in the z-buffer. We don't use the regular pixel format defined in the Presentation Parameters. Instead, we use a special format for z-buffers. This format is D3DFMT_D16. This means that each pixel is 16-bit. There are other formats, but we will not need them for the extent of this tutorial.

2. Turning On Z-Buffering

This one is quite simple. We have just one function to call, with two simple parameters.

If you did the last lesson, you might recall using the SetRenderState() function. Well, we're going to use it again, meaning two calls will be made. Here is what we now have:

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

This time we set the first parameter to D3DRS_ZENABLE, which enables z-buffering. We could, of course, set it to FALSE, but this would turn the z-buffering off, which would not be desirable.

3. Clearing the Z-Buffer

This also takes just one function, and this function is also one you have seen before, and are therefore using twice. This function is the Clear() function.

Previously, you used this function to clear the back buffer, like so:

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

This cleared the back buffer to black. Well, we also want to clear the z-buffer, so we change one parameter and do this:

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

The only change here being that the D3DCLEAR_TARGET was changed to D3DCLEAR_ZBUFFER. Simple.

Notice that the color is still there. When clearing a z-buffer, the Clear() function uses the parameter after the color. This is 1.0f, which is the "farthest" a depth buffer can be.

The Finished Program

And now let's look at the final program. In addition to adding z-buffering, I changed a few things in the pipeline, because this program does not rotate a single triangle, but shows two identical rotating triangles, one right behind the other. These changes are not bolded. If you have not yet mastered matrices, it might be a good idea to study the new pipeline code in addition to the bold buffer code, to see how this works.

[Main.cpp]

If you run this program, you'll get two triangles rotating around each other. This is a screenshot of what you'll get:

The Rotating Triangles
Summary

Now we are getting somewhere! Next, we'll learn to apply textures to our primitives, as well as combine them to make shapes more interesting than mere triangles. This particular lesson does not have anything major to learn, but I'd suggest modifying the code until you are familiar with it, then doing these exercises:

1. See what happens when you turn z-buffering off
2. See what happens when you clear the z-buffer to 0.0f instead of 1.0f
3. Study and change around the new transform code to become familiar with how what was done

When you're done (or if you're skipping out), let's find out how to combine these triangles to make simple geometric shapes in actual 3D!

Next Lesson: Simple Modeling

GO! GO! GO!