3D Programming Demos and Tutorials

3D Transformations
by Simon Brown

next part

What now?
Now we are going to look at how to move the objects in your world, and how to move the camera. We're also going to explore the world of matrices. This type of 3D engine views objects along the positive z-axis. The camera is always looking straight down the z-axis in the positive direction. If the camera was actually allowed to turn and move itself, it would make the 3D to 2D transformation extremely complicated. Instead of rotating and moving the camera, we rotate and move the whole 3D world in the opposite direction.

You are probably thinking this sounds crazy, but this is how polygon 3D engines work. This is how almost every 3D game you've ever played works. Even if you wrote an engine that didn't work this way, you will still need to be able to scale, translate and rotate the objects in your 3D world. We will look into how to do this in a second but first I want to talk about object (local), global and camera space.

Object, Global, Camera Space and the 3D Engine Pipeline
When you are building your 3D meshes or objects, you define them in their own space about their own origin. This is known as local, object or sometimes model space, but we will call it object space. This means when you are building your models, you should center them about the origin of all three axis (0, 0, 0). The first transformations you perform on your objects will be performed in object space.

If you need to scale your objects, then you do this in object space and it is the first transformation you carry out.

You will almost definitely want to rotate your objects at some point and this is also carried out in object space.

After those two steps are completed you need to translate your objects into global space. Translating means moving, or sliding, so the rotation and size don't change, only the position. Global space is where you put your objects in relation to each other. So once your global space is complete you have your entire 3D world complete with everything in the correct position in relation to everything else, with the exception of your camera.

The final transformation is to take account of the position and rotation of the camera. So to get your objects from global space to camera space you need to rotate and translate everything in your 3D world again. This final step is the one that gets the part of your world which should be visible sat along the positive z-axis so we can view it with our 3D to 2D transformation.

Matrices
So the type of transformations we need to be able to carry out are scaling, rotating and translating. The easiest way to do this is with matrices, specifically 4 by 4 matrices. A matrix is a rectangular array of numbers with rows and columns. We can refer to a particular element by specifying the row followed by the column, like M23 etc. So a general 4 by 4 matrix will look like this-

Matrix M = [ M11 M12 M13 M14 ]
           [ M21 M22 M23 M24 ]
           [ M31 M32 M33 M34 ]
           [ M41 M42 M43 M44 ]

and a specific matrix will look like this-

Matrix A = [ 1 3 5 1 ]
           [ 2 3 2 2 ]
           [ 1 0 0 1 ]
           [ 0 0 0 1 ]

Of course there would be no point in using matrices if they didn't have a practical use. Certain types of matrices can be used to perform geometric transformations, like scaling, rotating and translating. What we have to do is generate a matrix for each transformation. The good thing though is that matrices are cumulative when multiplied, so if we generate a scaling matrix and a rotational matrix and then multiply them together, then the resulting matrix will perform the same transformation as both the scaling and rotational matrix together, which is handy because we don't need to store all the matrices we will be needing (this is called matrix concatenation). We will come to multiplying matrices and how to transform a 3D point by a matrix later, but first I want to show you the kinds of matrix we will be dealing with.

Scaling Matrix
The first type of matrix we will be using is a scaling matrix, which looks like this-

Scaling Matrix M = [ Sx 0  0  0 ]
                   [ 0  Sy 0  0 ]
                   [ 0  0  Sz 0 ]
                   [ 0  0  0  1 ]

This matrix will scale a 3D point in relation to it's origin in object space by the factors Sx, Sy and Sz in the three axis x, y and z. So if you want to double the size of a 3D object you would generate a scaling matrix with Sx, Sy and Sz each of 2. In other words-

Scaling Matrix M = [ 2  0  0  0  ]
                   [ 0  2  0  0  ]
                   [ 0  0  2  0  ]
                   [ 0  0  0  1  ]

 

Rotational Matrix - X
To rotate a 3D point about the origin we need three matrices, one for each axis of rotation, x, y and z. The matrix to rotate about the x-axis looks like this-

X Rotational Matrix M = [  1     0     0     0  ]
                        [  0   cos A  sin A  0  ]
                        [  0  -sin A  COs A  0  ]
                        [  0     0     0     1  ]

where A is the angle of rotation about the x-axis.

 

Rotational Matrix - Y
To rotate a point about the y-axis we use the following matrix-

Y Rotational Matrix M = [ cos A  0  -sin A   0  ]
                        [  0     1     0     0  ]
                        [ sin A  0   cos A   0  ]
                        [  0     0     0     1  ]

again where A is the angle of rotation, but this time about the y-axis.

 

Rotational Matrix - Z
To rotate a point about the z-axis we use this matrix-

Z Rotational Matrix M = [  cos A sin A  0    0  ]
                        [ -sin A cos A  0    0  ]
                        [   0     0     1    0  ]
                        [   0     0     0    1  ]

once again where A is the angle of rotation, about the z-axis.

 

Translation Matrix
The last type of matrix we need to learn about is a translation matrix. Remember translation means moving or sliding, changing position but not rotation or size. We use a translation matrix to move from object to global space, and also as part of moving from global to camera space. A translation matrix is as follows-

Translation Matrix M = [   1     0     0    0  ]
                       [   0     1     0    0  ]
                       [   0     0     1    0  ]
                       [   Tx    Ty    Tz   1  ]

where TX, Ty and Tz are the distances along the x, y and z axis that we want to translate our objects.

 

Multiplying Matrices
After we generate the first matrix, a scaling matrix, we don't need to do anything with it, but at each stage after that, for instance when we generate the three rotational or the translation matrix, we need to multiply the result we get with the previous matrix. So in the end we will have a single matrix which will do the same thing as all five matrices together. So we clearly need to be able to multiply two 4 by 4 matrices.

Instead of explaining how to do this, I'm just going to show you. Matrix multiplication is extremely messy, and the only matrices you will ever be multiplying is 4 by 4 matrices. If you really need to understand the maths behind it then get hold of a good maths book. The following code shows a function that will multiply two matrices A and B and return the result. Obviously how you store a matrix is entirely up to you, the simplest way would be something like this-

struct matrix
{

float m [4][4];

};

Assuming we are using the above struct to represent a matrix, then our matrix multiplication routine would be as follows-

// Function to multiply matrix A by B and return the result

matrix MultiplyMatrices ( matrix A, matrix B )
{

   matrix result; // temporary matrix

   for ( int i = 0; i < 4; i++ )
   {

      for ( int j = 0; j < 4; j++ )
      {

         result.m [i][j] = 0;
         for ( int k = 0; k < 4; k++ )
         {

            result.m [i][j] += B.m [i][k] * A.m [k][j];
         }
      }
   }

   return result;
}

So that's matrix multiplication covered. We could now generate all five matrices (scaling, x rotation, y rotation, z rotation and translation) and we could multiply the result each time (apart from the first time) by the previous result and end up with once single matrix which would perform all five tasks. The only thing left now is how to process 3D points using this resultant matrix.

Transforming 3D points using matrices
Ok, so we've worked out our five matrices, multiplying them at each stage and ended up with one final matrix. We can now use it to process any 3D coordinates it applies to, namely the 3D coordinates of the current object you are processing.

To transform any point in 3D space (x, y, z) by the 4*4 matrix M, i.e..

[x' y' z'] = [x y z] [ M11 M12 M13 M14 ]
                     [ M21 M22 M23 M24 ]
                     [ M31 M32 M33 M34 ]
                     [ M41 M42 M43 M44 ]


we do

x' = ( M11 * x) + ( M21 * y ) + ( M31 * z ) + M41
y' = ( M12 * x) + ( M22 * y ) + ( M32 * z ) + M42
z' = ( M13 * x) + ( M23 * y ) + ( M33 * z ) + M43


That's the maths, so the code would look like-

// matrix A contains the result of scaling, rotational and translation matrices combined

new_x = ( A.m[0][0] * x ) + ( A.m[1][0] * y ) + ( A.m[2][0] * z ) + A.m[3][0];
new_y = ( A.m[0][1] * x ) + ( A.m[1][1] * y ) + ( A.m[2][1] * z ) + A.m[3][1];
new_z = ( A.m[0][2] * x ) + ( A.m[1][2] * y ) + ( A.m[2][2] * z ) + A.m[3][2];

So that's the basics covered. You should now know how to generate a scaling, x, y, z-rotational and translation matrix. You also know how to multiply 4 by 4 matrices and you now know how to use them to process 3d points. With the maths I have shown you in these first three tutorials you should be able to define a simple shape like a cube in 3D space, position in where you want it, and draw it.

Since I'm trying to show you the maths and not a complete working program, I'm inevitably leaving out a lot of the little details, like the data types you might want to use, and how to organize the whole engine. In the last section of this tutorial I will (hopefully) show you how to put it all together. Before we get to that though, I want to introduce you to the idea of surface normals and back-face culling which is the subject of the next tutorial.

All content copyright © Simon Brown 1999-2005.
back to the introduction