Camera Translation and Rotation
download mac os x application and source code (76k)
read "controls" for keys to move and rotate camera
First released thursday february 7, 2002. Developed with apple's free developer tools on mac os 10.1
This document, images, source and application are the intellectual property of forrest briggs. You may freely use the source code i provide. Email me with questions or suggestions.
Theory
In triangle rasterizing 3d engines, triangles are transformed into camera space, then projected onto the screen. We can take a much more direct approach to camera movement in ray tracing.
Changing the location of the camera is as simple as changing the origin of the camera rays. The camera can be rotated by rotating the direction of the camera rays as though they were points.
Until now, i have been using the following simple equations to rotate primitives:
X-axis:
Y' = y * cosθx - z * sinθx
Z' = z * cosθx + y * sinθx
Y-axis:
Z' = z * cosθy - x * sinθy
X' = x * cosθy + z * sinθy
Z-axis:
X' = x * cosθz - y * sinθz
Y' = y * cosθz + x * sinθz
Where (x,y,z) is the original point, (x',y',z') is the new point and θ is the angle. An important thing to notice here is that for x-axis rotation, y is used in the calculation of z', so make sure to use The original value (y) not the new value (y'.) The same is true of the other axes. We cannot use these formulas effectively for camera movement because they can only rotate around the x, y and z axes of world Space. To illustrate this point, first turn your head slightly to the right, then tilt up. You first rotated your head around the y-axis, then rotated around the x-axis, but not the x-axis of world space. You Rotated around the x-axis in head-space. In order to rotate around any axis, we must use matrices, which are also more efficient.
Matrices
Matrices are covered in depth many other places on the web (try searching on google,) so i will only describe the parts of them that are relevant to rotation.
This is in no way a complete description of matrices and should either be supplemented with previous knowledge or other reading.
It may be easy for programmers to think of matrices as 2 dimension arrays of floating point numbers. Matrices are written like this:
[m00 m10 m20] M =[m01 m11 m21] [m02 m12 m22]Where mxy denotes the element of m at position (x,y) and m is a 3x3 matrix. Math books will describe the elements of a matrix with (row, column) which is the same as (y,x) and start counting at 1. Since matrices are represented in C++ as 2 dimensional arrays, it is convenient to start at 0 and use (x,y) addressing. All of the math will come out the same though.
In order to multiply matrices, the x dimension of the first matrix must be the same as the y dimension of the second. The resulting matrix has the x dimension of the second matrix and the y
Dimension of the first matrix. Each element of the resulting matrix is the dot product of the row of the first matrix with the same y coordinate as the element and the column of the second matrix with the same x.
In such terms, matrices will warp your mind. An example will help:
[1]
[4 7 8] * [5] = [((4*1) + (7*5) + (8*2))]=[(4 + 35 + 16)]=[55]
[2]
The resulting matrix is 1x1 because the second matrix has an x dimension of 1 and the first matrix has a y dimension of 1. Matrix multiplication is not commutative, meaning that a * b is not always the same as
B * a. It is associative, so a * b * c = a * (b * c), which will be important later.
Here is the code to multiply 2 3x3 matrices:
Matrix3x3 MatrixMult3x3(Matrix3x3 &a, Matrix3x3 &b) //returns ab
{
Matrix3x3 result;
For(int i=0; i<3; i++)
For(int j=0; j<3; j++)
Result.Elements[i][j] = a.Elements[0][j] * b.Elements[i][0] +
A.Elements[1][j] * b.Elements[i][1] +
A.Elements[2][j] * b.Elements[i][2];
Return result;
}
|
Inline Vector VectorMultMatrix(Vector &a, Matrix3x3 &b)
{
Vector result;
Result.X = a.X * b.Elements[0][0] + a.Y * b.Elements[0][1] + a.Z * b.Elements[0][2];
Result.Y = a.X * b.Elements[1][0] + a.Y * b.Elements[1][1] + a.Z * b.Elements[1][2];
Result.Z = a.X * b.Elements[2][0] + a.Y * b.Elements[2][1] + a.Z * b.Elements[2][2];
Return result;
}
|
Rotation Matrices
Matrices can be used for rotation. For example, the matrix for rotation about the x-axis is
[1 0 0 ]
R = [0 cosθ -sinθ]
[0 sinθ cosθ ]
If a point or vector p = [x y z] then
[1 0 0 ]
P' = p * r = [x y z] * [0 cosθ -sinθ] = [x (y * cosθ - z * sinθ) (z * cosθ + y * sinθ)]
[0 sinθ cosθ ]
Which is the same as the equation for x-axis rotation previously described. Here is the matrix for rotation around any axis:
[(t * x2 + c) (t * x * y - s * z) (t * x * z + s * y)]
R = [(t * x * y + s * z) (t * y2 + c) (t * y * z - s * x)]
[(t * x * z - s * z) (t * y * z + s * x) (t * z2 + c) ]
When c = cosθ, s = sinθ, t = 1 - cosθ and (x,y,z) is a normalized vector in the axis of rotation. Here is a discussion of this matrix that goes into quaternions, which are 4 dimensional complex numbers.
Implementation
Now we can rotate the rays by constructing a rotation matrix and using VectorMultMatrix() to multiply the direction of the rays by that matrix, resulting in the correctly rotated direction.
This matrix will need to be updated every time the camera rotates, and it should be initialized so that if a ray's direction is multiplied by it, the direction will not change at all. For this, we need
The identity matrix (i.)
[1 0 0]
I = [0 1 0]
[0 0 1]
The identity matrix has the property that if a matrix m of the correct dimensions is multiplied by it, it will not change. In other words, m * i = m.
The camera data structure contains 3 vectors: right, up and forward. These vectors define the direction that the camera is pointing and are initialized to right = (1,0,0), up = (0,-1,0) and forward = (0,0,1).
These values may differ depending on your coordinate system.
Once the camera is set up, we can rotate it with CameraRotate()
Void CameraRotate(Camera &camera, float thetaRight, float thetaUp, float thetaForward)
{
Matrix3x3 rotationRight, rotationUp, rotationForward;
// calculate rotation matrices about each axis that defines the camera's direction
RotationRight = CalculateArbitrayRotationMatrix(camera.Right, thetaRight);
RotationUp = CalculateArbitrayRotationMatrix(camera.Up, thetaUp);
RotationForward = CalculateArbitrayRotationMatrix(camera.Forward, thetaForward);
Matrix3x3 combinedRotationMatrix;
// combinedeRotationMatrix = rotationRight * rotationUp * RotationForward
//combinedRotationMatrix = MatrixMult3x3(MatrixMult3x3(rotationRight, rotationUp), rotationForward); gives a compiler error
CombinedRotationMatrix = MatrixMult3x3(rotationRight, rotationUp);
CombinedRotationMatrix = MatrixMult3x3(combinedRotationMatrix, rotationForward);
// rotate the defining axes by the combined rotation matrix
Camera.Right = VectorMultMatrix(camera.Right, combinedRotationMatrix);
Camera.Up = VectorMultMatrix(camera.Up, combinedRotationMatrix);
Camera.Forward = VectorMultMatrix(camera.Forward, combinedRotationMatrix);
// combine the current ray rotation matrix with the new matrix
Camera.RayRotationMatrix = MatrixMult3x3(camera.RayRotationMatrix, combinedRotationMatrix);
}
|
The function CalculateCameraRay() takes the screen coordinate through which we want a ray and like the previous examples, uses a lookup table to get the direction of that ray. Then it multiplies that direction
By the ray rotation matrix to make it point in the rotated direction.
Inline Ray CalculateCameraRay(Camera &camera, int x, int y, Vector *directionTable)
{
Ray ray;
Ray.Origin = camera.Location; // maybe optimize this statement
Ray.Direction = directionTable[x + (y<<9) + (y<<7)];
Ray.Direction = VectorMultMatrix(ray.Direction, camera.RayRotationMatrix); // rotate the ray
Return ray;
}
|