Most of the examples so far assume that you are drawing a single convex polyhedron, which means that backface removal via culling is sufficient to remove hidden surfaces. However, if you draw more than one polyhedron, or a non-convex solid, backface removal isn't enough. Depending on the camera position, two faces that are both visible may overlap, and it won't always be clear which one should be drawn in front of the other.
Direct3D provides a Z-buffer that you can use to sort out the problems that arise when two faces overlap in this way. The Z-buffer keeps track of the distances from the camera to each face in the image in the direction of every pixel displayed. When it draws a new face, it checks the distance from the camera to the face for each pixel. If the face is farther away than some other face at that pixel, then Direct3D leaves the pixel's color alone—in other words, it shows the previously drawn closer face. If the new face is closer than any other face drawn so far, Direct3D colors the pixel for the new face and updates the Z-buffer with that face's distance.
|Figure 12. Dangerous Intersection: The d3dIntersectingTetrahedrons program uses a Z-buffer to display intersecting solids correctly.|
The result is that each pixel drawn in the image has the proper color for the non-culled face closest to the camera and solves the problem of multiple solids, non-convex solids, and even faces that pierce each other.
sample program shown in Figure 12
draws two tetrahedrons that intersect. It uses a Z-buffer to draw all of the faces correctly. (In fact, it's interesting to turn off the Z-buffer and see what happens.)
This program works much as the previous examples do with just a few changes. First, its CreateVertexBuffer
method creates different data. It builds a triangle list containing the eight triangles that draw the tetrahedrons. Each triangle is properly oriented so Direct3D can cull triangles that are oriented counterclockwise.
Next, the InitializeGraphics
method adds two parameters to the PresentParameters object it uses to create the graphics device. The following code shows the two new parameters:
params.EnableAutoDepthStencil = True ' Depth stencil on.
params.AutoDepthStencilFormat = DepthFormat.D16
also turns on Z-buffering with the following code:
' Turn on the Z-buffer.
m_Device.RenderState.ZBufferEnable = True
To make viewing the tetrahedrons more interesting, the SetupMatrices
method for this example uses a different world transformation than the previous examples. Rather than rotating the data around the Y-axis, it uses the following code to rotate the data around a line that passes through the origin in the direction of the vector <1, 1, 1>:
m_Device.Transform.World = Matrix.RotationAxis( _
New Vector3(1, 1, 1), CSng(angle))
In the previous examples, the Render method started by clearing the drawing surface. In this example, it must also clear the Z-buffer so Direct3D can start recording fresh distances between the camera and the faces. This code clears both the drawing target and the Z-buffer:
' Clear the back buffer and the Z-buffer.
m_Device.Clear(ClearFlags.Target Or ClearFlags.ZBuffer, _
Color.Black, 1, 0)
Finally, the Render method uses a slightly modified call to DrawPrimitives so it can draw the eight triangles in its triangle list:
|Figure 13. Locked Blocks: The d3dLockedBlocks program uses 36 triangles to draw three interlocked blocks.|
PrimitiveType.TriangleList, 0, _
The sample program d3dLockedBlocks, shown in Figure 13
, uses similar code to display 36 triangles that define three interlocked blocks.
Working with the Examples
Direct3D is a lot to digest all at once. Building a Direct3D device isn't too difficult, but it is a fairly persnickety process and you need to get a lot of details correct.
Unfortunately, Direct3D error messages tend to be somewhat vague. Many errors throw an InvalidCallException. If you examine the exception, the more detailed message is often "Error in the application," which is about as helpful as telling a baseball player going up to bat, "triple to left."
To make matters worse, the exception may appear far from the code that caused it. For example, if you forget to call BeginScene
, the program works fine until you try to draw something with a call to DrawPrimitives
If you follow the examples here carefully—or just copy and modify the sample applications and give them new data—you should be able to get them running relatively quickly. After you master the six drawing primitives supported by Direct3D (point list, line list, line strip, triangle list, triangle strip, and triangle fan), you can draw all kinds of complicated scenes.
These examples use explicitly defined colors for every face displayed. For a complex scene, you would need to define a large number of colors very carefully to prevent adjacent triangles from blending into each other in strange ways. To see what I mean, run program d3dDrawTriangleStrips
and select solid shading. All the triangles in the surface turn green, so all you'll see is a big blob.
The solution to this problem is to use texture and lighting models to allow Direct3D to define the colors of the triangles for you. If you define materials and lights properly, the result is more realistic and requires less work on your part.
Defining textures and lighting is the subject of the next article in this series on Direct3D. Until it is posted, you might want to go to Microsoft's DirectX Graphics page
to learn more. If you come up with some interesting Direct3D applications, email me at RodStephens@vb-helper.com
and let me know. I'll post them so others can enjoy them and be inspired to make their own creations.