Spaces and Matrix in Direct3D
Direct3D requires 3 matrices to be set before 3D geometry can be rendered. These matrices are used to transform the 3D geometry from its initial definition in model space into the final image drawn on the screen in 2D screen space.
The steps the geometry goes through are:
- You initially define your 3D object in an editor (like 3DS max) or by hard coding the values. You normally set 0,0,0 as the centre of your object with vertex positions defined relative to the centre. These positions are defined in Model Space
- We want to position, rotate and scale our 3D model into our game world. In order to do this we must provide a matrix that Direct3D can use to convert the vertex positions from model space into this new space, known as World Space. This matrix is called the World Matrix
- We need the world to be transformed so it appears as if it is being viewed from a certain position in our world. This position is the location of the eye or camera. So we provide a matrix that allows Direct3D to convert from World Space into View Space (sometimes known as camera space). This matrix is called the View Matrix
- Finally we need to tell Direct3D how to project this 3D View Space onto our flat 2D screen. So we provide a matrix that allows Direct3D to convert from View Space into Screen Space. This matrix is known as the Projection Matrix.
So the three matrix:
- World Matrix - Transforms 3D data from Model Space into World Space. You need to set this before rendering every entity in your world.
- View Matrix - Transforms from World Space into View Space. You need to set this each time your camera changes position
- Projection Matrix - Transforms from View Space into Screen Space. You normally set this just once during initialization.
Direct3D provides many functions to help you create these matrices. Some are described below along with common settings and examples.
OpenGL and Direct3D vary in axis system. Direct3D uses a left handed system where X is right, Y up and Z in to the screen while OpenGL has X right, Y up and Z out of the screen. To convert between the two systems you can invert any one of the axis (but only one). I normally invert Z but inverting either of the others is also valid. You cannot convert from one of these coordinate systems to another using rotations.
Note: these are the two most commonly used coordinate systems but there are others e.g. 3DS Max has Z up Y into the screen and X right. To convert from left handed to the 3DS system you swap Z and Y coordinates.
Direct3D defines a matrix type called D3DXMATRIX. This is a structure that contains 4 by 4 elements for the matrix values. You can declare one simply:
This creates a variable of type D3DXMATRIX and calls it worldMatrix.
There are many functions in Direct3D you can call that can alter matrix in certain ways. One important one is D3DXMatrixIdentity, this sets a matrix to be an identity matrix. It is often a good idea to initialise a matrix to a default identity. An identity matrix if applied in a transform leaves the 3D data unchanged, so to set the above declared matrix to identity you would do this:
D3DXMatrixIdentity( &worldMatrix );
Your world matrix is used to transform from model space into world space. You use it to take your 3D models and position, scale and rotate them into the world. Model space is the space you created your model in e.g. in an art package where 0,0,0 is normally the centre of the object and vertex positions are defined relative to this. You want to place this object into your game world so you need to translate, rotate and scale it into world space. There are a number of helper functions that you can use to create this matrix:
D3DXMatrixTranslation(D3DXMATRIX *out, FLOAT x, FLOAT y, FLOAT z );
This function takes a pointer to a matrix as its first parameter and then 3 values used to position the object in the world. So if you want your object to be located at 10,0,50 you would code:
After this call the matrix is filled with the values required to carry out this transform.
D3DXMatrixRotationX(D3DXMATRIX *pOut, FLOAT angle)
D3DXMatrixRotationY(D3DXMATRIX *pOut, FLOAT angle)
D3DXMatrixRotationZ(D3DXMATRIX *pOut, FLOAT angle)
These three functions allow you to set up your matrix to rotate the object around one of the axis. You use the function you require and specify the rotation angle in radians. E.g. to rotate a object 180 degrees around the Y axis we could write:
D3DX_PI is a defined value for PI, PI is 180 degrees in radians. See the note at the end of this section about converting between them: Radians and Degrees
The final thing you can do with your world matrix is specify a scale. So you could make your object twice as big by scaling it with 2,2,2.
D3DXMatrixScaling(D3DXMATRIX *out, FLOAT sx, FLOAT sy, FLOAT sz );
Again you pass in the address of your matrix and then specify a scaling value, so to make your 3D object twice as big you could do this:
Using the above functions you can position, rotate and scale your 3D object into your world. There is one important thing to note however and that is that you must do these matrix operations in a certain order. In Direct3D you must combine your matrix in SRT order (scale * rotation * translation).
Therefore if you want to position your entity at position pos and you have three rotations for: around the x axis (pitch), around the y axis (yaw) and around the z axis (roll) you could do it like this:
// Calculate rotation matrix
D3DXMatrixRotationX( &matRotX, rot.x ); // Pitch
D3DXMatrixRotationY( &matRotY, rot.y ); // Yaw
D3DXMatrixRotationZ( &matRotZ, rot.z ); // Roll
// Calculate a translation matrix
// Calculate our world matrix by multiplying the above (in the correct order)
// Set the matrix to be applied to anything we render from now on
device->SetTransform( D3DTS_WORLD, &matWorld);
The view matrix transforms from world space into view space. It allows you to specify a position in the world where the view will be seen from. This needs to be set whenever the camera position is changed.
Camera control is in a separate topic (see camera), here I will just describe a simple method for creating the view matrix. The function builds a left handed view matrix from some inputs:
D3DXMatrixLookAtLH(D3DXMATRIX *out, CONST D3DXVECTOR3 *eye, CONST D3DXVECTOR3 *at, CONST D3DXVECTOR3 *up );
- out - this is where we pass in the address of the matrix we want filling
- eye - the world position of the eye point
- at - the position in the world we are looking at
- up - defines which way is up. This is needed because if you are positioned somewhere in the world looking in a certain direction you could be upright or standing on your head and in each case the position and direction would still be the same. So to determine which way up we are we need to provide it here
An example of creating and setting a view matrix to represent the camera positioned at 0,3,-5 looking at 0,0,0 and upright (0,1,0):
// Initialise our vectors
D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
// Get D3DX to fill in the matrix values
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
// Set our view matrix
device->SetTransform( D3DTS_VIEW, &matView );
Note: when implementing a camera system you will not want to use this function, see the camera notes.
The projection matrix specifies how our 3D view data is transformed onto our 2D screen. This is where we can specify the perspective to use. We use the following function that builds a projection matrix for us:
D3DXMatrixPerspectiveFovLH(D3DXMATRIX *out, FLOAT fovY, FLOAT Aspect, FLOAT zn, FLOAT zf );
- out - this is where we pass in the address of the matrix we want filling
- fovY - this is the field of view we require. D3DX_PI/4 is commonly used here.
- Aspect - this is the aspect ratio we require. A common method is to divide the screen width by its height
- zn - this is the z value you want for your near plane. Anything that is closer than this value is not shown. You would normally set this to 1.0f
- zf - this is the z value of the far clipping plane. Anything in your world that is further away than this will not be shown. So if your world is 100 units in size you will want this value to be at least 100.
Note: this matrix is normally only set once in a game, at the start unless you are doing any fancy effects or techniques like rendering to a texture.
An example of creating and setting this matrix might be:
FLOAT fAspect = ((FLOAT)800) / 600;
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 1.0f, 1000.0f );
gD3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
Radians and Degrees
PI radians is 180 degrees. 2 PI is 360 degrees. Often it is easier to think in terms of degrees rather than radians. To convert between the two you could create two macros in a header file, like this:
#define DEGTORAD(degree) ((D3DX_PI / 180.0f) * (degree)) // converts from degrees to radians
#define RADTODEG(radian) ((180.0f / D3DX_PI) * (radian)) // converts from radians to degrees
Then you could rewrite the world matrix rotation as:
There are three matrix used by Direct3D to transform your 3D models into the final 2D image you see on the screen. They are the World Matrix, the View Matrix and the Projection Matrix. Note: If you are coming from a OpenGL background OpenGL combines the first two together.
You create your 3D model in Object Space. You apply a world matrix to transform from object space into world space. To take the camera into account you apply a view matrix to transform from world space into view space and finally you apply a projection matrix to transform from view space into screen space.
Direct3D provides a number of functions that allow you to easily create your matrix.
The world matrix needs to be set to position every entity you render
The view matrix is set each time the camera changes position or orientation
The projection matrix is set just once at the start of your game