Direct3D 8 Tutorial #1
DirectX is the name of the Microsoft technology that allows you to write computer games that achieve a high frame rate (i.e., refresh rate). DirectX allows your program to write to the video display much faster than Microsoft's original technology, the GDI ("graphics device interface"). Direct3D is that portion of DirectX which can draw three dimensional (3D) shapes with photorealistic shading and scientifically correct lighting. With Direct3D, you can create a program that allows the operator to dynamically adjust his position with respect to a 3D object, to zoom in and out, and to rotate the object about its central axis. You can even fly right through the object to view its interior.
I have resisted using any of Microsoft's DirectX technologies in my educational software because DirectX is typically not installed on the computers found in public schools. But I couldn't resist a side project where I would learn enough about Direct3D to accomplish a program that allows the user to fly through a 3D model. I call this effort my first Direct3d tutorial even though I have no plans for a second.
I decided to employ Direct3D 8, which is the version that ships as part of the Windows XP operating system. Direct3D 8 can also be installed under Windows 98/ME/2000 but it cannot be installed under Windows 95 or Windows NT.
To develop Direct3D programs you need to obtain the DirectX SDK (software development kit) from Microsoft. This is a free download from Microsoft but because it's over 200 Mbytes it is still a pain to acquire. To merely execute a program that employs Direct3D you only need the much smaller DirectX run-time which you can again download from Microsoft. Alternatively, most computer games install some version of the DirectX run-time and hence your computer may already have what you need. Once you have installed the DirectX 8 run-time, you should be able to execute any program that employs DirectX 8, DirectX 7, DirectX 6, etc.
The first project I tackled with Direct3D was a model of the Munsell color solid. The Munsell color solid is a model for the range of colors perceivable by the human eye. It was developed back in 1905 by Albert H. Munsell. It is called a color solid because the color varies continuously throughout the volume of the model. You usually see the Munsell color solid displayed in 2D space in one of the following manners:
A vertical slice through the model would result in a 2D image such as the following:
The darkest, least-colorful colors reside near the center of the Munsell color solid. In fact, the central axis is the range of black and white colors known as the gray scale. The outer perimeter of the model is lumpy, just because that's how Munsell decided to distribute the colors. Munsell was an artist. Most scientists would prefer a color model that displays some form of geometric symmetry, such as a sphere or pyramid. But there is no reason to believe that cleaner geometry better depicts human color perception.
My goal was to use Direct3D to display 20 radial slices through the Munsell color solid and to allow the operator to select an arbitrary rotation, tilt, and zoom factor from which to view this representation of the color solid. A typical view presented by the finished program is shown below:
If you have the DirectX 8 run-time installed on your computer and would like to try this program then click here to download my Direct3DTut1.exe executable onto your computer. While the program is running you will be able to use the r and R keys to rotate the model, the e and E keys to elevate your viewpoint above or below the centerline, and the z and Z keys to select your viewing distance (z for zoom).
The remainder of this web page is aimed at C++ programmers who would like to see some Direct3D 8 code that illustrates transparent textures and the importance of the alpha test.
A texture is just a .BMP file used to provide the surface coloring for a 3D model. For example, my Direct3DTut1 program uses 20 .BMP files holding the radial slices through the Munsell color solid. A single one of these .BMP files is shown below:
You can see that this .BMP file has a medium gray background. Microsoft's D3DXCreateTextureFromResourceEx() function provides a way to declare one particular color as the color key color which means that it won't be copied to the screen. The way you accomplish this in Direct3D8 is by enabling alpha blending via a statement such as:
pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true );
The color key color will then be transparent wherever it appears in your textures. While running my Direct3DTut1 program you can type the t key to toggle whether the medium gray color is treated as being transparent. When the color is not treated as transparent, the Munsell color solid looks like:
Obviously, that's a mess. But even after I got the transparent texture working correctly, I still observed the following, smaller mess:
You really need to download my Direct3DTut1.exe executable and rotate the model for yourself to observe the dynamic characteristic of the image distortion pictured above.
At first it seemed that the distortion visible in this last image only occurred on the left-hand side of the Munsell color solid. And it so happens that you are viewing the back face of the Direct3D surfaces on that side of the Munsell color model. You can verify this by typing the b key to toggle Direct3D's back face culling mode. If the distortion only existed on the left-hand side of the color solid then the problem might be an interaction between transparent textures and back faces.
But this was a red herring, as I realized when I saw that if I rotated the model just so then there was a similar type of image distortion that would appear on the right hand side of the Munsell color solid. You can see this in the picture below:
I realized that what I was looking at was the gray "transparent" portion of the very first radial slice that my algorithm was painting to the screen. All the other radial slices would look great on the right-hand side of the Munsell color solid except for this one particular slice, and this bad slice was the very first slice I painted.
I was able to solve this problem and eliminate the visual distortion by adopting a more complicated rendering algorithm. My original program always painted slice #0 first, then slice #1, etc. irregardless of the current rotation of the model. The visual distortion disappeared when I took the trouble to paint the radial slices in an order dictated by the current rotation of the model. As long as the slices were rendered in a back-to-front order the final picture was perfect. You can see the difference this makes by using the p keystroke to toggle between the original algorithm and the smarter algorithm that orders the slices.
After posting this code on the Internet, I was informed of an even simpler solution by Henrik Rydgård of Sweden. If the particular video card in the computer supports an alpha test then the visual distortion can be avoided even if the program continues to use the original algorithm where it always starts painting with slice #0.
To understand the alpha test, you first need to understand the mechanism that was causing the visual distortion. Let's suppose that slice #0 is the red slice (which it is) and the current rotation of the model means that you should be able to see some of the pink slice appearing behind the red slice (look again at the last picture). You need to be able to see the pink slice in those areas of the screen where the gray "transparent" border of the red slice would normally be painted. You prevent the gray pixels that make up the border of slice #0's texture from being copied to the display surface by declaring that gray should be treated as the color key. BUT EVEN THOUGH THE GRAY PIXELS ARE NOT COPIED TO THE DISPLAY SURFACE, THEIR POSITIONS IN 3D SPACE ARE WRITTEN TO THE DEPTH BUFFER !! Because of this, when you later attempt to create the pink pixels of slice #1 that need to appear behind slice #0, Direct3D will NOT paint these pixels because their z distance from the viewer is further than the distance recorded in the z buffer, leading Direct3D to believe these pixels are obscured by something closer to the viewer. But that something is transparent.
At first I didn't think Direct3D offered a solution to this problem. But it does, as I learned from Henrik. You just need to add the following statements:
pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, true );
pd3dDevice->SetRenderState( D3DRS_ALPHAREF, 0x01 );
pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL );
These 3 statements ask Direct3D to NOT write a transparent pixel to the z buffer (depth buffer) unless its alpha value exceeds a certain value. The particular value 0x01 is sufficient for my textures. If the transparent pixels from slice #0 are not written to the depth buffer then the (non-transparent) pixels from later slices can still qualify for rendering and thereby appear UNDERNEATH the transparent areas of slices that were drawn earlier. After adding these 3 statements I was able to toss the complexity of sorting the slices in a back-to-front order and instead I could always render slice #0 first, then slice #1, etc. But again, this worked on my computer because my video card offered this capability.
In order that other C++ programmers can experiment with this Direct3D code I make available a .ZIP file holding my full source code as well as a Visual C++ 6 workspace (.DSW file) here.