Hamburg (Germany), the 25th May 1998. Written by Nils Pipenbrinck aka Submissive/Cubic & $eeN

Doing a 3D-engine without a camera is quite easy. If you are writing your first 3D-engine or have never dealt with the camera stuff you might have problems figuring out how to do the camera transformation. As usual it turns out to be very simple.

We don't need that much information to define a camera. The only values are:

- The position of the camera itself
- The camera's look-at point
- the roll angle (rotation) of the camera around it's view axis

I made myself a structure which looks like this:

struct tcamera { tvector position; tvector target; float roll; };

Now, how do we calculate something useful out of these informations? First
we have to move the entire world so that the position of the camera becomes the
origin of the world coordinate system. This is done by translating by
the tcamera.position scaled by -1.
Then we have to find 3 vectors that form our new coordinate system. The three
vectors must all have a length of one, and must not be colinear.

The first vector which can be calculated is our new y-vector. It's the vector
that will point to the top of our screen. Calculate it by rotating a y-vector
around the z-axis by the roll-value:

Up.x = sin(roll); Up.y = -cos(roll); Up.z = 0;

Now we calculate the new z-vector. This vector has the same direction as the camera look direction, but it must be normalized to get the length = 1.

Forward.x = Look_at.x - Position.x Forward.y = Look_at.y - Position.y; Forward.z = Look_at.z - Position.z; normalize_vector (Forward);

The third vector can be calculated by the cross-product of Up and Forward. It'll result in a vector pointing to the right of our screen, or - speaking in our new coordinate system - to the positive x-axis. Normalize this vector, since it's not guaranteed that up and forward are collinear.

Right = crossproduct(up, forward); normalize_vector (Right);

We're almost finished now... But there is still one thing: It's not guaranteed that the up-vector and the direction-vector are colinear. (think about a camera that'll look at the sky). To avoid distortion we have to recalculate the Up vector again:

Up = crossproduct (Right, forward);

Again, normalize this vector (just in case, it doesn't hurt).

How do we build a rotation-matrix out of these three vectors? We only have to put the vectors into the three rows of our rotation-matrix.

| right.x right.y right.z | | up.x up.y up.z | ; (rotation3x3) | forward.x forward.y forward.z |

If you like 4x4 matrices (as I do) you might use:

| right.x right.y right.z 0 | | up.x up.y up.z 0 | | forward.x forward.y forward.z 0 | ; (rotation4x4) | 0 0 0 1 |

We also know the Camera translation matrix (which can only be built if you use 4x4 matrices since translation can not be expressed as a 3x3 matrix):

| 1 0 0 -position.x | | 0 1 0 -position.y | | 0 0 1 -position.z | | 0 0 0 1 |

The final Camera matrix is simply the translation multiplied with the rotation:

matrixmul (final, translation, rotation);

Assuming that you use 4x4 matrices for your object transformation you can just concatenate (multiply) your object matrix with the camera matrix. That's all. If however you still use 3x3 matrices you have something more to do:

- Subtract Pivot-Point from Point (if any)
- Multiply point with object_matrix (rotation + scaling)
- Add Pivot-Point from Point (if any)
- Add Object-Position to point
- Subtract Camera-Position from Point
- Multiply point with camera-rotation-matrix

The resulting point can then be clipped and projected. I think this is a good example of why 4x4 matrices are better than 3x3 matrices. Transforming points using 3x3 matrices is almost three times as expensive as using 4x4 matrices. Of cause the setup-costs (4 matrix-multiplications per object) are higher when you use 4x4 matrices. If you want to rotate a bunch of cubes 3x3 might be the better choice. (anyway, who draws cubes these days?).

I hope you learned something. Well, there might still be a bug in the math. It's possible that
this camera model will mirror on some axis (I haven't tested it). If so you only have
to change the order of the crossproduct somewhere. And as usual there are hundreds of
typos in the text.. You know: *foreign languages are difficult languages*.
Instead of flaming me you might press the »Save As« button, fix the typos and
send it back to me... thank you.
Maybe I'll add a tutorial which explains 4x4 matrices in detail. I think
there are still a lot of folks out there who don't see the advantage and only
use them cause they're *state of the art*.

colinear means, that a vector can not be expressed as the scaled summ of any other vectors. For example the vector (1,0,0) can not be expressed by either (0,1,0) or (0,0,1). Three vectors that are colinear form a basis for a 3d coordinate system.