SetupRotation is a new function. Here we call the functions D3DXMatrixRotationX, D3DXMatrixRotationY and D3DXMatrixRotationZ to generate rotation matrices and store them in three D3DXMATRIX structures. Next, we multiply the three matrices together to form one world matrix, we call the SetTransform function to apply the transformation to our vertices.
SetupCamera
SetupCamera is a new function. Here we setup the camera. We set it's position in 3D space to be (0, 0, –30) and point it at the origin (0,0,0). We have centred our cube around the origin. We also specify that the y axis points up. We use the D3DXMatrixLookAtLH to generate the view matrix and then use the SetTransform function to apply the transformation.
SetupPerspective
SetupPerspective is another new function. Here we setup the camera's lens. We have decided to have a field of view of PI/4 (normal) and an aspect ratio of 1. We have decided to set the near clipping path to 1, this means that polygons closer than one unit to the camera will be cropped. We have also decided to set the far clipping path to 500, this means that polygons more than 500 units away will be cropped.
Render
In the Render function, we call the three new functions SetupRotation, SetupCamera and SetupPerspective. These functions are called before we render the polygons.
We render the polygons by using three triangle strips, one for the top, one for the sides and one for the bottom.
That's it for another tutorial. In this tutorial we learnt about Backface Culling, Matrices, 3D World Space and how a cube is made up using triangular polygons. In the next tutorial, we will arrange our code so far into classes and make our application full screen.
DirectX Tutorial 4: Full Screen and Depth Buffers
In this tutorial we will convert our single file into two classes: CGame and CCuboid. CGame will contain the main code such as initialisation, the game loop and rendering. CCuboid will be used to create cuboid objects, you can specify position and size. CCuboid also has a Render function that should be called in the CGame render function. I have not made any major changes to the code from the last tutorial, so you should find it pretty easy to understand. We will change our program to be full screen rather than windowed and we'll take a look at depth buffers. From now on, I will not show all of the program code in the tutorial as I have done so far (they're getting to long). Instead, I'll only show you the snippets of code that are new or have been modified. You can download the full source code by clicking the "Download Source" link above.
Using a Depth Buffer (also called a z-buffer) ensures that polygons are rendered correctly based on their depth (distance from the camera). Lets say, for example, that in your scene you have two squares – one blue and one green. The blue one has a z value of 10 and the green square has a z value of 20 (the camera is at the origin). This means that the blue square is in front of the green one. A depth buffer is used to make sure that where one object is in front of another, the correct one is rendered. DirectX will test a pixel on the screen against an object to see how close it is to the camera. It stores this value in the depth buffer. It will then test the same pixel against the next object and compares it's distance with the value held in the depth buffer. If it is shorter, it'll overwrite the old value with the new one, otherwise it will be ignored (there is something in front of it). This will determine what colour the pixel will be, blue or green. Fig 4.1 below, shows this for a given pixel on the rendering surface.
Fig 4.1
To use a depth buffer in your program is pretty easy. All you need to do is select the correct format in your InitialiseD3D function, enable depth buffering and ensure that you clear the depth buffer in your Render function. In the source for this tutorial (download above), I have added some code to select a depth buffer format in the InitialiseD3D method of CGame. There are two properties of the D3DPRESENT_PARAMETERS structure that need to be set:
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
d3dpp.EnableAutoDepthStencil = TRUE;
To enable depth buffering, you need to add the following line to your InitialiseD3D method.
m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
Then in the Render method of CGame, make sure that you clear the depth buffer as well as the back buffer by adding the D3DCLEAR_ZBUFFER flag to the device clear method, shown below.
m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
To make our program full screen we need to make a few adjustments to our InitialiseD3D method. We need to change the "Windowed" property of our D3DPRESENT_PARAMETERS structure to FALSE, this specifies that the program will run full screen. Then, we need to set two full screen properties of the D3DPRESENT_PARAMETERS structure. FullScreen_RefreshRateInHz controls the refresh rate of the screen, and FullScreen_PresentationInterval controls the maximum rate that you swap chain (back buffer chain) are flipped. We have selected D3DPRESENT_INTERVAL_ONE which specifies that flipping will only occur when the monitor refreshes.
d3dpp.Windowed = FALSE;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE;
Also, we need to select the correct back buffer format for our device. I have written a method, CheckDisplayMode, which will test for a valid format. This method is called in InitialiseD3D.
D3DFORMAT CGame::CheckDisplayMode(UINT nWidth, UINT nHeight, UINT nDepth) {
UINT x;
D3DDISPLAYMODE d3ddm;
for (x = 0; x < m_pD3D->GetAdapterModeCount(0); x++) {
m_pD3D->EnumAdapterModes(0, x, &d3ddm);
if (d3ddm.Width == nWidth) {
if (d3ddm.Height == nHeight) {
if ((d3ddm.Format == D3DFMT_R5G6B5) || (d3ddm.Format == D3DFMT_X1R5G5B5) || (d3ddm.Format == D3DFMT_X4R4G4B4)) {
if (nDepth == 16) {
return d3ddm.Format;
}
} else if((d3ddm.Format == D3DFMT_R8G8B8) || (d3ddm.Format == D3DFMT_X8R8G8B8)) {
if (nDepth == 32) {
return d3ddm.Format;
}
}
}
}
}
return D3DFMT_UNKNOWN;
}
The only other thing we need to do is modify our WinMain function that created our window. We need to set the x and y positions of the window to be 0 (top left) and we need to change the width and height to the screen size using the GetSystemMetrics function.
I have also added the ability to create a log file, this can prove useful when debugging. I have added to methods to CGame: EnableLogging and WriteToLog. I call EnableLogging in WinMain just after the CGame object has been created, this method clears the current log and enables logging. WriteToLog simply writes text to the log file if logging is enabled. When the program closes down, statistics are also added to the log file. You should notice that the frames per second stat is no higher than your monitor refresh rate, this is due to setting d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE. The log file is called "log.txt" and should be in your project folder once the code has been run for the first time.