 Model Space Introduction So far we've only used Transformed (Screen Space) vertices. Transformed vertices have a number of limitations: To move them, you have to lock the Vertex Buffer and re-write the position values Lighting (which we haven't covered yet) doesn't affect Transformed vertices. No perspective. Objects with small Z values are the same size as similar objects with large Z values. Transformed vertices still have their place, but most often you will use Untransformed vertices defined in Model Space. What is Model Space? As mentioned in the 3D Primer, Model Space vertices are defined relative to each other, often with (0,0,0) in the center. When we used Screen Space coordinates it was obvious what each unit was equal to, 1 pixel. In Model Space you can define your units to be whatever size you want them to be. They can be inches, feet, meters or light-years, whatever scale suits your needs. Because most games are done in a human-relative scale, 1 meter is a very common unit. Also, while it's not required it's a good idea to make your units consistent. If 1 unit is 1 meter for 1 model, make it the same for each other model as well as your World Space units. If your Model Space units and World Space units are different then you'll have to scale them when they are rendered. To render using Untransformed vertices 3 matrices need to be set up: the World Matrix, the Projection Matrix and the View Matrix. Additionally, there is the Viewport which is used to build a 4th matrix internally. First I'll show each is built, then I'll explain how they work together. View Matrix The View Matrix is our camera. We have to define its position and its orientation. The position is simply a 3-element vector of floats that represent it's x,y,z position in World Space. This vector is often called the Eye Vector. The orientation is given by 2 vectors, the Look At Vector and the Up Vector. The Look At Vector is a 3-element vector that defines the point that the camera is looking at. This is used to calculate the viewing angle and the actual distance between the Eye and Look At points doesn't matter. The up vector points, well, up. Typically this will be set to (0,1,0) which points along the Y-axis. But if you wanted your camera (and thus your scene) to be upside down, then you could set it to (0,-1,0). Various other values will allow you to simulate banking and other effects. The D3DX library has a function called D3DXMatrixLookAtLH that we can use to create our View Matrix. D3DXVECTOR3 eye_vector,lookat_vector,up_vector; D3DXMATRIX view_matrix; //View point is 8 units back on the Z-axis eye_vector=D3DXVECTOR3( 0.0f, 0.0f,-8.0f ); //We are looking towards the origin lookat_vector=D3DXVECTOR3( 0.0f, 0.0f, 0.0f ); //The "up" direction is the positive direction on the y-axis up_vector=D3DXVECTOR3(0.0f,1.0f,0.0f); D3DXMatrixLookAtLH(&view_matrix, &eye_vector, &lookat_vector, &up_vector); Once we have our View Matrix built, we have to register it with our device. This is done with the SetTransform method. g_d3d_device->SetTransform(D3DTS_VIEW,&view_matrix); The Projection Matrix If the View Matrix is our camera, the Projection Matrix would be the camera lens. D3DXMATRIX *D3DXMatrixPerspectiveFovLH( D3DXMATRIX *pOut, float fovY, float Aspect, float ZNear, float ZFar ); pOut This is where our matrix is returned when it has been built. fovY Field of View, in radians. For a standard camera, this is π/4 or 45 degrees. Aspect The Aspect ratio of the display. Width/Height. ZNear Near view plane, distance from the camera in World space units. Anything that is closer to the camera will be clipped (not drawn). This also defines the precision/scale of the Z buffer (more on that in a later lesson). ZFar Far view plane, distance from the camera in World space units. Anything farther will be clipped. This also affects the Z buffer along with the Near Plane. D3DXMATRIX projection_matrix; float aspect; aspect=((float)g_width / (float)g_height); D3DXMatrixPerspectiveFovLH(&projection_matrix, //Result Matrix D3DX_PI/4, //Field of View, in radians. aspect, //Aspect ratio 1.0f, //Near view plane 100.0f ); //Far view plane g_d3d_device->SetTransform(D3DTS_PROJECTION, &projection_matrix); World Matrix The World Matrix transforms your vertices from Model Space to World Space. The transforms that can occur include Translation (movement), Rotation, and Scaling. Each of these transforms is built using a separate matrix which is then combined to make your final World Matrix. Matrices are combined by multiplying them together. It's important to note that the order that you multiply the matrices is important. The order in which they are multiplied gives the order in which the operations are performed. Rotating then Translating will cause the model to spin on it's own axis, then get moved. Translating then Rotating will move the model and then cause it to revolve around it's origin. In this lesson we don't want to move the mesh at all. To do this we set our World Matrix to the Identity matrix, which is a NOP, or "Do Nothing" transform. In this case our Model Coordinates are our World Coordinates. D3DXMatrixIdentity(&world_matrix); g_d3d_device->SetTransform(D3DTS_WORLD,&world_matrix); Viewport The Viewport defines the area of the screen that will be rendered to. By default it is set to the entire display, so we don't need to change it. The Viewport can also be used to scale Z values, but we won't touch on that here. This is the Viewport structure. struct D3DVIEWPORT9{ DWORD X; DWORD Y; DWORD Width; DWORD Height; float MinZ; float MaxZ; }; If we wanted to change the Viewport we'd initialize the structure and then call the SetViewport method of our device. Though we build it as a structure, it gets converted into a matrix internally. If you needed to set our Viewport to the default, this is how you could do it. int g_width,g_height; //Pre-initialized Width and Height of the display Direct3DDevice9 *g_device; //Pre-initialized device D3DVIEWPORT9 view_port; view_port.X=0; view_port.Y=0; view_port.Width=g_width; view_port.Height=g_height; view_port.MinZ=0.0f; view_port.MaxZ=1.0f; g_device->SetViewport(&view_port); Piecing the Transforms Together To take your mesh from Model Space to Screen Space (where it can be rendered) the vertices will undergo 4 transformations. Here is a brief overview of the process. The World Transform takes your Model Space vertices,which are usually centered on (0,0,0) and are defined in arbitrary units, and transforms them into World Space. World Space has the world centered on (0,0,0) and is also defined in arbitrary units. These units do not have to be the same as your Model Space units, but it makes things much easier. Then the View Matrix is used to transform your World Space vertices into View (or Camera) Space. In View Space the viewer is at the origin (0,0,0) and is looking in the direction of the positive Z-axis. After that the vertices are transformed into Projection Space using the Projection Matrix. In Projection space X and Y coordinates are scaled to be between -1 and 1, while the Z coordinates range from 0 to 1. Finally, the viewport is used to transform the vertices into Screen Space where they can be rendered. Perspective In the introduction we mentioned that Screen Space coordinates lack perspective. In some cases that's a feature, other times it is not what you want. The Projection Matrix we supplied was created by a call to D3DXMatrixPerspectiveFovLH. As the name implies, this matrix is responsible for applying Perspective to our scene. If we wanted to use the flexibility of Untransformed Coordinates while not having Perspective we could create an Orthogonal Projection. We'll show how to do that in a later lesson. Lighting Lighting isn't covered in this lesson, but it has to be mentioned. By default Untransformed vertices are lit by Direct3D. Since we haven't added any lights, our scene would be rendered all black and we wouldn't see anything. To prevent out vertices being lit (by our 0 lights) we turn off lighting. This is done with a call to SetRenderState. g_d3d_device->SetRenderState(D3DRS_LIGHTING,FALSE); Lost Devices As mentioned in previous lessons, there are resources and states that do not survive a device Reset. Two of these are Transforms and RenderStates. To handle this we initialize our matrices and set our RenderStates in the InitVolatileResources function so they will be set up after a device Reset. Since neither one of these allocated resources there is still nothing to do in FreeVolatileResources. Wrapping Up This early in the game it's not important that you memorize all the information we covered in this lesson. For the most part you'll simply set up your matrices and not worry about it. Still, it's nice to understand why and how things work. When you get to more advanced topics you will need to understand this stuff. I didn't want to cover too much ground in one lesson, that's why we simply leave the World Matrix as Identity. In the next lesson we'll go into more detail with Translation, Rotation, and Scaling. Lesson Downloads Source and Executable for C++ (124K) Source for C++ (10K) To compile this lesson you will also need the Drunken Hyena Common Code MSDN Links For Functions/Concepts Discussed Here Back