Camera in Direct3D

These notes describe one method of implementing a first person camera using Direct3D.

There is some source code to accompany these notes that you can download here: CameraDemo.zip (NEW). This shows a world axis made up of teapots (and why not!) and a camera that can be rotated and translated.

From the matrices notes we know that the view matrix is used in Direct3D to position and orientate the camera (or eye point if you like). We would like to be able to position our camera anywhere in the game world and orientate it to point in any direction with 3 degrees of freedom.

View Matrix

For the view matrix we can use the D3DXMATRIX structure defined in the d3dx9.h header file. It has a 4 by 4 array of floating point entries. 4 columns and 4 rows. An example of how it is declared:

struct {
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;
}

You can access any element in the matrix using the first number as the row and second as the column. So to set the 3rd row, 4th column to a value of 1.0f :

D3DXMATRIX mat;
mat._34=1.0f;

When the matrix is used as the view matrix we can break it down into 4 orthogonal vectors: The look vector that represents the direction the camera is pointing. The up vector which says which direction is up. The right vector that points to the right of the camera and finally the position element that says where the camera is positioned in the world. The break down of the final view matrix is:

 

1

2

3

4

1

Right.x

Up.x

Look.x

0.0f

2

Right.y

Up.y

Look.y

0.0f

3

Right.z

Up.z

Look.z

0.0f

4

-position.right

-position.up

-position.look

1.0f

The last column is always 0,0,0,1
Note: row 4 is the negative dot product of the position and each of the axis vectors.

Therefore these vectors describe the position and orientation of our camera in the world:

Camera up, look and right vectors

D3DXVECTOR3 up,look,right;

As you can see it is just like any axis e.g. our normal world x,y,z axis. It is useful to think about the camera as having its own axis. To orientate the camera we need to rotate this axis, always maintaining its orthogonal shape. We change the orientation of this axis in the world to change the direction the camera is pointing and we position it in the world to move our camera.

We are going to manipulate these three vectors and when it comes to create the view matrix we just fill the vectors into the matrix using the table above as a guide.

Rotating the camera

To rotate the camera we need to rotate the up, look or right vectors. To do this we create matrix describing our rotation and transform the relevant vectors by it.

We will call rotation around the look vector (or z axis) Roll. Rotation around the up vector (or y axis) is called Yaw. Rotation around the Right vector (x axis) is called Pitch. Below is a picture of an aircraft showing roll, pitch and yaw in terms of the aircraft's local axis. Note: we can use a similar system for our object rotations.

roll, pitch and yaw

To summarise what we know so far:

  • The view matrix is used to position and orientate the camera in world space
  • The view matrix is made up of Up, Look and Right vectors and a Position element
  • These vectors form an orthogonal camera axis, by rotating these vectors we can change the axis orientation in the world
  • We rotate the axis by creating rotation matrix and transforming axis vectors using it
  • We call rotation of the up vector (y) Yaw
  • We call rotation of the look vector (z) Roll
  • We call rotation of the right vector (x) Pitch.

Direct3D View Matrix Calculation

There are a few approaches to implementing a camera system. The method I describe below is the one I tend to use and is the one that is used in the accompanying demo. I will talk about other methods afterwards.

For this method we keep up, look and right vectors as members of our camera class. When we come to create our view matrix though we reset these vectors to their start values. This means we need to store accumulated values for yaw, pitch and roll (as opposed to changes). Therefore our camera class may have the following member data:

D3DXVECTOR3 position;      // camera position
float yaw;                 // rotation around the y axis
float pitch;               // rotation around the x axis
float roll;                // rotation around the z axis
D3DXVECTOR3 up,look,right; // camera axis

Each time we calculate our view matrix we set our axis back to the initial values of up pointing up y, look pointing down z and right pointing down x:

CalculateViewMatrix(...)
{
  up=D3DXVECTOR3(0.0f,1.0f,0.0f);
  look=D3DXVECTOR3(0.0f,0.0f,1.0f);
  right=D3DXVECTOR3(1.0f,0.0f,0.0f);

Yaw rotation

To be able to apply our Yaw rotation (rotation around the up axis)  we need to create a matrix capable of carrying out this rotation. We do this by calculating the matrix as a rotation around the up axis

D3DXMATRIX yawMatrix;
D3DXMatrixRotationAxis(&yawMatrix, &up, yaw);

The D3DXMatrixRotationAxis function is a really useful D3DX function that takes a vector and an angle and produces a matrix that will provide that requested rotation.

Note: All Direct3D calls that take an angle require the angle to be in radians not degrees. To convert see this answer.

To apply yaw we rotate the look & right vectors about the up vector using our matrix:

D3DXVec3TransformCoord(&look, &look, &yawMatrix);
D3DXVec3TransformCoord(&right, &right, &yawMatrix);

camera up, look and right axis

Pitch rotation

For pitch we create a matrix capable of rotating around the right axis:

D3DXMATRIX pitchMatrix;
D3DXMatrixRotationAxis(&pitchMatrix, &right, pitch);

We apply our pitch matrix by rotating the look and up vector around the right vector using our matrix:

D3DXVec3TransformCoord(&look, &look, &pitchMatrix);
D3DXVec3TransformCoord(&up, &up, &pitchMatrix);

Roll rotation

For roll we create a matrix capable of rotating around the look axis

D3DXMATRIX rollMatrix;
D3DXMatrixRotationAxis(&rollMatrix, look, roll);

We apply our roll matrix by rotating the up and right vectors around the look vector using our matrix:

D3DXVec3TransformCoord(&m_right, &m_right, &rollMatrix);
D3DXVec3TransformCoord(&m_up, &m_up, &rollMatrix);

Filling the view matrix

Once we have created our rotation matrices and applied them to our axis vectors we are ready to fill in the view matrix. As well as rotation we need to fill in the position of the camera.

D3DXMATRIX viewMatrix;
D3DXMatrixIdentity(&viewMatrix)

viewMatrix._11 = right.x; viewMatrix._12 = up.x; viewMatrix._13 = look.x;
viewMatrix._21 = right.y; viewMatrix._22 = up.y; viewMatrix._23 = look.y;
viewMatrix._31 = right.z; viewMatrix._32 = up.z; viewMatrix._33 = look.z;

viewMatrix._41 = - D3DXVec3Dot( &position, &right );
viewMatrix._42 = - D3DXVec3Dot( &position, &up );
viewMatrix._43 = - D3DXVec3Dot( &position, &look );

This can be difficult to visualise so try drawing these things or even better try creating some axis out of paper and manually seeing how these rotations work.

Finally we have to actually set the view matrix:

gD3dDevice->SetTransform( D3DTS_VIEW, &viewMatrix );

Camera rotation

To rotate our camera we can now simply provide functions in our camera to add to our rotation variables e.g.

void Yaw(float amount)
{
   yaw+=amount;
}

Since with the described method I am storing accumulated angles you may also want to check for the angle going over 360 degrees (2*PI radians) or below 0.

Camera movement

With the above system we always have up, right and look vectors and position stored as member variables of the class. This makes it very easy to move the camera in the correct direction e.g.

position+=look*movementAmount;    // Move forward
position+=look*-movementAmount;    // Move backward
position+=right*movementAmount;   // Move right (strafe)
position+=right*-movementAmount;   // Move left
position+=up*movementAmount;      // Move up
position+=up*-movementAmount;      // Move down

Where movementAmount is a floating point scalar value determining how far we should move.

Note: if you implemented your camera without holding the camera axis you could use the view matrix instead e.g. to move forward you would add to the position the result of multiplying the view matrix and a vector 0,0,z,0, where z is the speed to move.

Alternative methods

As I mentioned earlier there are other ways of creating a camera class. One way would be to not set the up, view and right vectors back to initial values each time we calculate the view matrix but instead maintain them as we go along. Using this method whenever we wanted to rotate we would carry out the rotations above straight away on our camera axis (rather than keeping accumulated values). The only problem with this method is that due to floating point inaccuracies after a number of rotations our camera axis starts to lose its nice orthogonal shape. To solve this we would need to rebuild the axis via the following steps:

  1.   normalising the look vector
  2.   creating the up vector from the cross product of the look and the right
  3.   normalising up
  4.   creating the right vector from the cross product of the look and the up
  5.   normalising right

For really smooth camera control Quaternions are recommended. Direct3D has a lot of support functions for using Quaternions. Please look in the help for further information.

First Person Camera

With a normal first person view you never want to roll the camera (unless perhaps you are implementing a lean) so there is no need to carry out that calculation and up will always point straight up (0,1,0). Often the y element of the position will be the height of the ground under the camera (or player).

Mouse Control

You could control the camera with the keyboard mouse. If you trap WM_MOUSEMOVE messages in your WndProc you can determine the position of the mouse on the screen and relate that to a change in camera angle (remember the previous mouse position and work with the difference). See the Win32 notes: WM_KEYDOWN and WM_MOUSEMOVE. Advanced control may use DirectInput.

Advanced Notes

  • If you are using Direct3D the camera is best held in a visualisation component. However other components will need to communicate with it e.g. tell it to 'Move Forward' so access functions will be required in the Visualisation class. This is better than providing direct access to the camera itself from outside the component. A more advanced solution would be to separate the visualisation camera calculations and simulation (world model) representations.



© 2004-2016 Keith Ditchburn