home

Tutorial Index

DirectX tutorial 6: Going 3D

 

            Now, we have defined a cube in 3D space, but there is a certain catch. We defined our flexible vertex format to contain RHW information, which means it was pre-translated into screen coordinates. DirectX thus did not perform any transform operations on it, but to visualize 3D data as, well, 3 dimensional, we need to do all those transforms.

Thus the first thing we do is we remove RHW from our definition of CUSTOMFVF and CUSTOMVERTEX (we are in 3Dheader.h), just remove RHW wherever seen. Next you have to also remove the values of RHW in the vertex declarations, so they match the size of the custom vertices. Finally, as I said, the cube was defined in screen coordinates, in 3D these would represent an immense item. Thus replace 200 with -1 and 400 with 1, this will create a cube with its corners at -1 and 1 of each axis, which is more reasonable. Also as you can understand, the center will be on 0, which means whatever displacement we apply will be the displacement perceived by the user (i.e. we define exactly where it is without calculating current position). If you to a search and replace operation, make sure that exactly 12 values are replaced, otherwise you’ve also changed other values in the program.

Now, we have to tell DirectX what is needed to be transformed. Go back to D3DLoader.h and add a function setMatrices(). Matrices are like tables or arrays, in our case they are two-dimensional, 4x4 arrays. They take into account RHW as a fourth element. Matrices are a fast way to perform operations on vectors and are generally the tool of choice in Computer Science for any operations (including approximation of mathematical functions, like the square root, derivation or integration and so on). If you want to learn more about matrices, I suggest you (after of course looking up the internet) watch the linear algebra lectures of MIT professor Gilbert Strang (available on YouTube as MIT Open CourseWare), and possibly also read his books on the subject, which are taught widely in universities.

In setMatrices() declare six D3DXMATRIX objects: objectM, translationM, rotationM, projectionM, lookAtM and finalM. You can separate their names with a comma. All functions we will use return an HRESULT value, so you can also declare an HRESULT variable if you want to do debugging. D3DXMatrixIdentity(&matrix); sets the matrix we pass to it (to be precise we pass it’s address) to the identity matrix, which when multiplied by anything it returns the same thing. You can use it to eliminate steps from the procedure to make sure the rest are working correctly. We will use it on objectM to begin with. D3DXMatrixRotationY(&matrix, rotation) will return a rotation around the Y axis; you can already guess what gives rotation around the X and Z axis. This we will use on rotationM. A safe way to work with rotations independent of whether we are using radians or degrees is the use of D3DX_PI, which is, as you probably know, 180 degrees, half a circle, or 3.14… radians. For this tutorial we will pas as rotation D3DX_PI/4. D3DXMatrixMultiply(&final, &matrix1, &matrix2) multiplies two matrices and gives the output in a third one. We will use finalM to store the output and we will multiply objectM with rotationM.

Next up, we define our field of view. This is where it is defined how narrow or wide is the area of the world we can perceive, as well as far we render. Though you may think that one may be enough, when using a telescope for example, you may need another one. In the occasion of the telescope, you need a narrower field of view, so things far stretch out along the scene. You cannot just draw from closer because you may skip obstructions. The functions is D3DXMatrixPerspectiveFovLH(&matrix, angle, aspectRatio, nearClipping, farClipping). Angle is what I said earlier, how wide or narrow is the section we can see. Aspect ratio refers to your window’s/screen’s aspect ratio, which is width divided by height. A bug that cost me a couple of weeks of debugging was that I was performing integer division, so it snapped to zero, make sure you cast to float, if you still see nothing, store it in a variable and check it’s value. Near and far clipping values are the distances at which we start and stop drawing. You may have seen, especially in older games, that some items pop up or fade into sight as you move ahead, also in many games in options you will see drawing distance. Having a very far clipping pane, though today is well feasible, may on occasion mean that you’ll be drawing too many things, leading to a drop in frame rate. The field of view we created is sort of like the properties of the camera, it’s lens let’s say.

Now we have to define our camera. It is not exactly one camera, rather where it is, what it’s pointing at, and which way is up. Each of these are vectors, which, as we may have said in the past, are points in 3D space. Create 3 D3DXVECTOR3 objects, named camera, cameraTarget and cameraUp. Set cameraTarget’s x, y and z member variables to 0, we are looking at the center of our world. The cameraUp variable is safe to assume to be (0, 1, 0), in XYZ of course. The camera position is more up to you, as long as you set the other two vectors up correctly you should be able to see the cube. I have set x to -10, y to 10 and z to 0. You can perceive this as saying that the camera is 10 points back (x = -10), 10 points up (y = 10) and is aligned with the x axis (z = 0, thus no sideways displacement).

Let’s set up our view matrix, or as we named it, lookAtM. The function is D3DXMatrixLookAtLH(&final, &cameraVector, &targetVector, &cameraUpVector); and we’ll pass lookAtM, camera, cameraTarget, and cameraUp respectively. This will create a matrix representing a camera in 3D space.

Now we are nearly set. Let’s get finishing with what he have ready. The SetTransform(id, &value) method of our Direct3D device sets the transform we specify with id, to the value we give it. Thus we will write: Pd3dDevice->SetTransform(D3DTS_WORLD, &finalM); and similarly with D3DTS_PROJECTION and projectionM, and D3DTS_VIEW and lookAtM.

The last part which seems essential, though many tutorials do not include it, is switching off the lights. No, this does not mean we’ll have darkness, on the contrary, we will see stuff, because DirectX will not be expecting a light, and will just show us what there is to be seen. Without this, I got  black mass of a cube. The code is simply calling pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE).

is should have you good to go. Call setMatrices in Rebder3D() and you should be able to see the cube in the window, I call it right after BeginScene(). If you can’t see the cube, I have also added another small chunk of code, which sets the viewport.

You will declare it as D3DVIEWPORT9 view_port, well, the name is up to you. Set it’s X and Y to 0, Width and Height to xRes and yRes respectively, and MinZ and MaxZ to 0.0f and 1.0f, which is the depth at which it can render, the numbers taken are sort of the relative depth of stuff. With multiple viewports you can draw multiple cameras in the same window. The Commandos series made use of this so the player could keep an eye on enemies while controlling his players. In Silent Hunter, at least in the latest installments, you see the explosion of the ships you sink in the upper right corner of the screen, regardless of where you are, this again is most probably by use of multiple viewports.

Finally, the complete code of setTransforms is:

void setMatrices()
{
//--------------transformation code----------------//
D3DXMATRIX objectM, translationM, rotationM, projectionM, lookAtM, finalM;

            HRESULT hr;

            D3DXMatrixIdentity(&objectM);
D3DXMatrixRotationY(&rotationM, D3DX_PI/4);
D3DXMatrixMultiply(&finalM, &objectM, &rotationM);

            D3DXMatrixPerspectiveFovLH(&projectionM,D3DX_PI/4,(float)xRes/yRes, 1, 100);

            D3DXVECTOR3 camera;
camera.x = -10;
camera.y = 10;
camera.z = 0;
D3DXVECTOR3 cameraTarget;
cameraTarget.x = 0;
cameraTarget.y = 0;
cameraTarget.z = 0;
D3DXVECTOR3 cameraUp;
cameraUp.x = 0;
cameraUp.y = 1;
cameraUp.z = 0;
D3DXMatrixLookAtLH(&lookAtM,&camera,&cameraTarget, &cameraUp);

            hr = pd3dDevice->SetTransform(D3DTS_WORLD, &finalM);
hr = pd3dDevice->SetTransform(D3DTS_PROJECTION, &projectionM);
hr = pd3dDevice->SetTransform(D3DTS_VIEW, &lookAtM);

            D3DVIEWPORT9 view_port;

            view_port.X=0;
view_port.Y=0;
view_port.Width=xRes;
view_port.Height=yRes;
view_port.MinZ=0.0f;
view_port.MaxZ=1.0f;

            pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

            //pd3dDevice->SetViewport(&view_port);
}

Download the code for this project here.

Bonus tasks:
-Change colors to make it more visible that the cube is 3 dimensional.
-Make the cube rotate as time passes.

 

Suggestion: Task 1 should look something like this:

First 3D sample