.:[ Please always use http://www.32bits.co.uk to access the site. Thanks! ]:.
Last updated: 10:55 AM Monday October 2008
![]()
Here we have what may potentially be the most boring tutorial you will ever have to read. Especially if you don't really like maths, and see it as a means to an end (like me). Unfortunately you need just the most basic understanding of the following concepts to achieve anything in D3D. So, with that in mind I'm going to introduce you to the "32Bits Cheaters Guide to 3D Maths"! This isn't meant to be an exhaustive introduction to basic math principles or D3D8 features by any means. The purpose of this tutorial is to introduce the concepts to you, and show you the methods and functions D3D provides to make your life easier.
I'm going to introduce 3 topics in this tutorial - planes, vectors and matrices. Out of these, matrices are the most important and I'll spend more time explaining them. Planes and vectors are fairly simple, and D3D makes things even easier so I'll cover those topics quite quickly. Matrices is the topic you'll want to pay most attention to, though.
First up let's talk about "planes". These aren't the big things that take you on holiday, they are a mathmatical construct that extend infinitely across any 2 axis. Here's a plane on the X & Z axis:
Fig 1.1: X-Z Plane
Okay, technically the diagram doesn't extend the plane infinitely across X and Z, but you get the idea. You'll have to excuse the diagrams in this section, because infinite planes are not easy to draw ;). There are 2 main uses for planes. Firstly, they are useful for collision detection as you can check wether a vector insersects a plane easily. Secondly, they are useful for reflection effects. To create the illusion of a reflection (a mirror, still water etc) you create a plane where the reflecting object will be, and then create a matrix which mirrors the vertices around the plane. Here we have a triangle which is mirrored on a Y-Z plane, meaning that we can move the plane (mirror) anywhere on the X axis we want:
Fig 1.2: Triangle mirrored on a Y-Z plane
To create a plane in D3D, you use the D3DXPLANE struct to store the plane itself. To set the plane data, the most common method is to use the D3DXPlaneFromPoints() function. As the name suggests, this creates a plane from the vertices you supply it. It's prototyped as follows:
D3DXPLANE* D3DXPlaneFromPoints(D3DXPLANE* pOut, CONST D3DXVECTOR3* pV1, CONST D3DXVECTOR3* pV2,
CONST D3DXVECTOR3* pV3);
You supply 3 D3DXVECTOR3 structs representing 3 vertices to this function, and it fills out a D3DXPLANE struct for you. By definition a plane can only exist on 2 axis, therefore all 3 of your vertices will have an identical X, Y or Z value. For example, the following code creates a Y-Z plane at 2 units on the X axis:
Fig 1.3: Y-Z Plane at 2 units on X axisD3DXVECTOR3 a( 2.0f, 1.0f, 1.0f );
D3DXVECTOR3 b( 2.0f, 0.0f, 0.0f );
D3DXVECTOR3 c( 2.0f,-1.0f,-1.0f );
D3DXPLANE plane;
D3DXPlaneFromPoints( &plane, &a, &b, &c );
Note how all 3 vertices have identical values for the X axis component. Because a plane is an infinite construct, I don't need to specify massive values for the remaining Y & Z vertices. If you're having trouble seeing how the above coordinates represent a Y-Z plane, plot the vertices on a piece of paper.The following equation is called the "Plane equation":
Ax + By + Cz - D = 0
This equation is calculated using something called the Dot Product of 2 vectors. Explaining this equation and the dot product is outside the scope of this tutorial, but this link to Amir's CFXWeb DP3 page should help. At the end of this tutorial you'll also find a link to the Flipcode Geometry Primer, which explains dot products, vectors and other calculations in depth.
Here are some of the more useful functions you can use with D3D planes. Search the SDK docs for "plane" for more.
D3DXMatrixReflect() - Builds a matrix which reflects vertices around a plane. Good for mirror effects etc.
D3DXPlaneFromPoints() - Builds a plane from 3 vertices
D3DXPlaneIntersectLine() - Returns where a line (vector) intersects a plane
The next item on the list is "vectors". We can define a vector in one of 2 ways. Firstly a vector can be "scalar". This means it represents all the information about a single point in space, and nothing more, just like a vertice of a 3D object. Secondly a vector can represent a magnitude and a direction, in which case it's just like an arrow - it points in a direction, and it's of a specific length. These types of vectors can be broken down further - free vectors, and bound vectors.Bound vectors are much easier to understand. They have a starting point, a direction, a magnitude (or length), and a sense. For example, I can say "This vector starts at (1,1,1) and ends at (10,12, 3)". In that sentance I have defined the starting point, the direction (as we know where the vector starts and ends, we know the direction it takes), and it's magnitude (the start position subtracted from the end position). I've also defined the sense, which tells us which way the vector is "facing" (as in, it's not going from the end position to the start position).
Fig 1.4: A vector
Conceptually a vector does not always have an end point defined.After bound vectors, we come to free vectors which are exactly the same as bound vectors except without a defined starting position. They are useful when all the points you are trying to describe are moving in the same direction (for example, imagine a starfield in an old 1980's spaceship game). If all the points are moving in the same direction, we can describe them with one single free vector instead of multiple bound vectors. So, free vectors have the same direction, magnitude and sense, but different starting positions. In reality everything has a starting point, so we generally use the origin (0,0,0) for this.
The next type of vector is called a unit vector, because it's magnitude is always 1. D3D uses these a lot, for example when you create the view transformation matrix for D3D you specify (0, 1, 0) as the Up vector - this is a unit vector. On occasion you may need to turn a normal vector into a unit vector, for example when you're performing lighting calculations. This operation is called "normalising".
The final type of vector is called a null vector, and always has a magnitude of 0. It therefore can only represent a single point in space, and has no sense, magnitude or direction. This is where the whole mystery of D3D calling vertices "vectors" comes together. When you specify a point/vertex on a 3D object, you're technically specifying a null vector.
It's nice to know that Microsoft just assume everyone understands maths inside out, and is happy to confuse you by calling all your vertices "vectors". So now you know - a vertex is just a null vector.
Here are some of the useful SDK functions and types for D3D vectors:
D3DXVECTOR2 - struct holding 2 floats for a 2 dimensional vector
D3DXVECTOR3 - struct holding 3 floats for a 3 dimensional vector
D3DXVECTOR4 - struct holding 4 floats for a 3 dimensional vector to operate with a 4x4 matrix
D3DXVecNTransform() - Transforms a D3DXVECTORN by a matrix
Matrices are the last and toughest piece of the puzzle. First, let me explain why we use matrices instead of more "conventional" maths. When you perform calculations using conventional maths, you apply them sequentially. For example, if we wanted to multiply 5 by 10 then divide the result by 2, we would do the following:5 * 10 = 50
50 / 2 = 25
That's plain and obvious. Now lets assume that we have a single null vector somewhere in space, and we want to move it right by 10 units and down by 3 units. In pseudocode, we would do the following:Vector.XPosition = Vector.XPosition + 10
Vector.YPosition = Vector.YPosition - 3
Simple stuff. Now image that this null vector is actually a vertex belonging to a 3D object, and that these calculations are to move the object around the screen. In this case our imaginary 3D object has 1,500 vertices, each one of which needs to be moved left by 10 units and down by 3 units. That means we're performing 3,000 transformations (one left move and one down move per vertice, multiplied by 1,500 vertices). Remember that each transformation is far more than just a simple calculation - the data has to be processed, passed through the API, processed by the GPU etc etc. Wouldn't it be great if, instead of performing two seperate transformations we could figure out a way to combine them into one single transformation? Instead of 3,000 transformations we would only require half that number to produce the same effect.Good news, there is! By using matrices, we can combine as many calculations as we want into one single operation. In other words, in practice this means we can move, rotate and scale a point all using one single matrix operation, instead of seperate calculations for the translation, the rotation and the scaling. We do this by creating one matrix for each operation we want to do. For example, to perform a rotation and scale, we create one matrix for the rotation and one matrix for the scaling. We then multiply these two matrices together, and the result is one single matrix which contains all the data required to move each vertice to it's final position. Multiplying matrices in this way is called "Matrix Concatenation". Just so you know, we can create these matrices by using D3DX functions - it's perfectly possible to work out the calculations ourselves, but why work extra hard when we already have a library to do the work.
Matrices are shown as an arrangement of numbers inside some curly brackets, the size of the matrix depending on the operation you wish to perform. The most common type of matrix we use in D3D is the 4x4 matrix, so called because it has 4 columns and 4 rows:
Fig1.5: A 4x4 Matrix
Fig1.5 shows a 4x4 matrix called matA. A little terminology for you - all the little "a"'s inside the curly brackets are called the "matrix components", and the little numbers below and to the right of each "a" signifies the position of the component in the matrix. So if you see "a41", you know that we're referring to the component at row 4, column 1 of the matrix in question.Now you need to learn a little theory about how matrix math works. Some of the time you'll find that the D3DX functions are more than enough to create the basic matrices you require, and to use them you don't need to know how all the following works. However, when you get a little more advanced you'll find the need to manually modify matrices, and understand the results of matrix calculations. With that in mind, I'll show you the basics of matrix math.
Matrix AdditionAdding matrices together is nice and simple, all we need to do is add all the components together. It's easier to see it than explain it:
Fig1.6: Matrix addition
As you can hopefully see, to perform matrix addition we simply add all the equivalent elements together. I've added some grey boxes to help deliniate each column. So a11 is added to b11, a12 is added to b12 etc. Here's a brief example with two 2x2 matrices:
Fig1.7: Matrix addition example
That's all there is to matrix addition! One point to remember, you can only add matrices in this way if they are of the same dimensions. Ie: adding a 3x3 matrix to another 3x3 matrix is fine, but you cannot add a 4x4 to a 6x6 matrix.
Matrix SubtractionIf you understood matrix addition, you'll immediately understand matrix subtraction because it's done in exactly the same way. I'm not going to go over it all again, I'll just show you Fig1.7 using subtraction instead:
Fig1.8: Matrix addition example
Nice and easy, just run through it yourself to make sure you understand how it works. At this point, I want to make sure you understand something. Take a look at the result of subtracting matA from matB. Now work out for yourself the final matrix components if you subtract matB from matA. It's a totally different result, right? This gives us a very important rule when working with matrices:
The order in which you perform mathmatical operations on matrices is extremely important
It's so important it merits a bold, double sized red line. Make sure you never forget that.
Matrix MultiplicationThis is the hardest type of matrix math, and you'll shortly regret ever needing to learn it. Once you understand it, though, you'll find it's easy enough to deal with. First up let's talk about how we multiply two matrices together - I'll show you how to transform a vector with a matrix in a second. As discussed above, the primary reason to use matrices is to allow us to "merge" multiple operations (rotations, translations etc) into one single operation. Taking a rotation & translation as an example, we do this by creating one matrix for the rotation we want, and one matrix for the translation we want. We then multiply the two matrices together, and the result of this multiplication is a matrix that rotates and translates in one operation.
The first rule of matrix multiplication is that you can only multiply matrices with the same adjacent dimensions. Take the following as an example:
Multiply a 4x4 matrix with another 4x4 matrix: 4x4 * 4x4 - This will work
Multiply a 4x3 matrix with a 3x4 matrix: 4x3 * 3x4 - This will also work
Multiply a 3x3 matrix with a 4x4 matrix: 3x3 * 4x4 - This will not work
Take a close look at the above 3 lines. I've taken each operation (column 1), and written down exaclty what I'm trying to do (column 2). I then look at the two numbers either side of the multiplication sign (highlighted) - the "adjacent" dimension numbers. If they match the matrices can be sucessfully multiplied, as shown in the first 2 examples. However if the adjacent dimensions do not match, as shown in example 3, you cannot multiply them together. That's worth another double size red line.
You can only multiply matrices with the same adjacent dimensions
That's the basic rule out of the way, now let's take a look at a simple matrix multiplication. To start, I'll show you a 2x2 matrix multiplied with another 2x2 matrix. There's no easy "english" way to explain this, so take a look at how we do it in practice. Here's the basic operation we're trying to perform - multiplying one 2x2 matrix with another to create a third 2x2 matrix:
Fig1.9: Matrix multiplication
To calculate matC, we multiply the rows of matA with the columns of matB and add the results together:
C11 = (A11 * B11) + (A12 * B21)
C12 = (A11 * B12) + (A12 * B22)
C21 = (A21 * B11) + (A22 * B21)
C22 = (A21 * B12) + (A22 * B22)
If you follow the table, it's pretty easy to work out. For a bit more clarity (as it's important you understand this), here is a step-by-step guide to multiplying two 2x2 matrices together:
Fig1.10: Practical Matrix multiplication
Wow, now you know why I showed you 2x2 matrices first ;). Take a close look at Fig1.10. See how we move across matA in rows, but along matB in columns? Here's a practical example:
Fig1.11: Practical Matrix multiplication #2
By now you should have 2x2 matrix multiplication sorted. As we know, when working with D3D and 3D we generally use 4x4 matrices. The good news is, multiplying 4x4 matrices is done in exactly the same way as 2x2 matrices. Here's a table to calculate a 4x4 matrix multiplication:
Fig1.12: 4x4 Matrix multiplication
Fig1.12 shows how you would matC, assuming matA * matB = matC. To demonstrate the calculation visually, I've highlighted the rows and columns in use for calculating matC row 1. As you can see, we multiply the first row of matA with each component of every column of matB to obtain the first row of matC. The procedure is exactly the same to calculate the remaining rows of matC, we just use the next row of matA and perform the same calculations. Run down the table, and it should become apparent exactly what we're doing. If not, you can just use the table to quickly lookup how to calculate any component of the 4x4 matrix when you need it. Now that you've seen a 2x2 matrix multiplication and a 4x4 matrix multiplication, you should be able to extrapolate the formula to multiply any size of matrix. For D3D8 though, we're only worried about 4x4 matrices.You're almost a matrix multiplication expert. There is one other topic we need to cover before we can move on. So far, we've only seen how to concatenate 2 matrices. That's all well and good, but what about the real world? All our 3D objects are made up from vertices, each one of which needs to be transformed by a matrix. So how do we transform a vertex using a matrix? And aren't 3D vertices just made up of 3 floats? How do we multiply a 3 float vertex with a 4x4 matrix?
It's easier than you may think! It's just like multiplying a 1x4 matrix with a 4x4 matrix. In fact, it's exactly like multiplying a 1x4 matrix with a 4x4 matrix because that's what you're doing. Before we continue, remember the rule - "you can only multiply matrices with the same adjacent dimensions". Because a vertex (null vector, remember!) is only 3 values, one for X, Y and Z, we need to fiddle the figures a little to make the adjacent dimensions match. We do this by adding an extra 1 to our vertice to "balance" the formula. You'll see this extra 1 referred to in the DirectX SDK Docs as "the reciprocal of homogenous W".
Fig1.12: Multiplying a vector by a matrix
In Fig1.12, we have a vertice (X, Y, Z). We add an extra 1 to the vertice to turn it into (effectively) 1x4 matrix, and perform our normal matrix calculation on it. The result is a vertice which has been transformed by a matrix. Simple or what! You'll see a practical example of this shortly.You are now officially a matrix math genius, and you're ready to see some real-life matrices in action.
Special Matrices
I'm about to show you a few types of useful matrices that will most definately come in handy. Yup, there's far more to matrices than a bunch of "a"'s in some brackets! First up, we have...
The Indentity Matrix
The identity matrix is one of those things that you never think of until you see it, whereupon you say "ohh yeah, of course!". There are times when you will want to create a matrix that performs absolutely no calculations at all. One very good example of this is the D3D8 world transformation. If you don't want to move objects around on the screen, you don't want to supply a matrix that affects vertice positions. Your first instinct may be to think, "okay, I'll just supply a matrix with zeros for every component". If you do that, you'll be in trouble.
Consider Fig1.12 for a moment. When we want to transform a vertice, we multiply it with a matrix. And of course preschool maths says, any number multiplied with zero equals zero. Therefore using an all-zero matrix is a very quick way to screw up your entire 3D scene. What you actually need is an identity matrix:
Fig1.13: A 4x4 identity matrix. It's all zeros and ones;)
That's all there is to it! I'm not going to explain how this matrix works. Instead I'm going to let you do the calculation for yourself. If you can sucessfully follow through a null vector (take any 3 numbers for an example) multiplied by a 4x4 identity matrix, you can be satisfied that you understand matrices well enough.
The Translation Matrix
It's all well and good looking at those theoretical matrices and identity matrices, but it's time we saw a matrix which actually does something. The following matrix transforms a vertice by the specified units:
![]()
Fig1.14: A 4x4 translation matrix
tX represents the number of units (remember, object/world space scales are defined by the coder) to translate the vertice on the X axis. tY represents the units to translate the vertice on the Y axis, and tZ represents the number of units to translate on the Z axis. Let's step through a practical example. Here I have a theoretical vertice at (11, 9, -2), and I want to apply a translation matrix to it that moves vertices right along the X axis by 2 units, down the Y axis by 1 unit and leaves the Z coordinate unchanged:
Fig1.15: Using a translation matrix to move a vertice (null vector)
Fig1.15 is your rough and ready reckoner to work out vertex (null vector) multiplications with matrices. The top of the diagram just recaps what we're trying to do - take a normal 3 component X,Y,Z vertice, multiply it with a translation matrix to produce a new vertice. The middle of the diagram shows a random vertice - (11, 9, -2) - with it's Homogenous W 1 added. We multiply it by a translation matrix to produce our new vertice - (13, 8, -2). The bottom of the diagram shows the steps I took to calculate the new vertice. I've split up the calculations into blocks of 2 lines, the top line showing the formula behind the calculation, and the lower line showing the calculation in practice for our example. By now you should have no trouble following the steps.You'll note that we don't bother to complete the final step of the calculation (using the extra 1 we added to our vertice for the homogenous W coordinate). This is simply because in the 3D world, we have no need for the result - performing the first 3 calculations provides us with a transformed vertice, so no need to waste time doing more work than we need.
The Scaling Matrix
Now that you've seen a translation matrix, here's a matrix that scales a vertice by a specified factor:
Fig1.16: A scaling matrix
sX, sY and sZ represent the scaling factor to apply to the X, Y and Z dimensions respectively. Conceptually, a scaling matrix is slightly harder to get your head around than a translation matrix. When we multiply a vertice by this matrix, what we're actually doing is translating it to a new position. We don't actually change the size of the vertice itself (as we know, a vertice is a null vector which has a magnitude of 0) we change the position of all the vertices that make up a 3D object so that the overall size of the object is scaled.Applying this matrix to a vertice is done in exactly the same way as the translation matrix example in Fig1.15. For your own practice, step through it with the scaling matrix to see how it works.
The X Axis Rotation Matrix
X, Y and Z axis rotation matrices look a little different to translation or scaling matrices, but they are calculated in exactly the same way. Here's an X axis rotation matrix:
Fig1.17: An X axis rotation matrixIn the diagram, A represents the angle to rotate in radians.
The Y Axis Rotation Matrix
As before, here's a Y axis rotation matrix:
Fig1.17: A Yaxis rotation matrix
Again, A represents the angle to rotate in radians.
The Z Axis Rotation Matrix
Finally, the Z axis rotation matrix:
Fig1.17: A Z axis rotation matrix
As you can see, apart from looking slightly different (and including some sin and cos calculations), these 3 rotation matrices operate in exactly the same way as any other matrix.
Now that you understand the principles behind matrices, you'll be happy to know that D3D hides all the implementation by using D3DX library functions. Here are some of the more common D3D types and functions to work with matrices - there are plenty more, just search the SDK docs for more info.
D3DMATRIX - Struct representing a basic 4x4 matrix
D3DXMATRIX - D3DX struct representing a 4x4 matrix, with extra functionality
D3DXMatrixTranslation() - Builds a matrix to translate a vector
D3DXMatrixRotationX/Y/Z() - Builds a matrix to rotate a vector around the X, Y or Z axis
D3DXMatrixScaling() - Builds a matrix to scale a vector
D3DXMatrixReflect() - Builds a matrix to reflect vectors about a plane
D3DXVec2TransformCoord() - Transforms a 2D vector by a matrix
D3DXVec3Transform() - Transforms a 3D vector by a matrix
Note the difference between D3DMATRIX and D3DXMATRIX. D3DMATRIX is a basic type which does not require the d3dx8.lib library, but does not include operator overrides and other extras that D3DXMATRIX (which requires the d3dx8.lib library) does.
And that, as they say, is that! This tutorial is by no means a complete explanation of everything to do with 3D maths, but it does show you the 3 major subjects you'll be using with D3D. If you understand how to calculate a matrix multiplication (even if you don't understand the maths behind how the matrices are created), you're in a good position to actually understand what you're going to be doing with D3D. Feel free to keep coming back to this page and use the tables to help you with any manual calculations you may want to do. But, as I've shown you, all of the maths is hidden behind D3DX libraries. So, although I wouldn't recommend it, you can quite happily forget exactly how all this works and just use the D3DX functions.I may expand this tutorial in future, but it's more likely that I'll discuss any new maths in the context of the tutorial that uses them. You now have the knowledge - use it wisely ;)
Here are some more links you may find interesting. I really recommend you read the first link - "Matrix and Quaternions FAQ":
- Flipcode Matrix and Quaternions FAQ
- Flipcode Geometry Primer
- SOS Maths
Feedback, comments and suggestions are always appreciated and go here.