First-person Camera

How to 3D

Chapter 7: Camera

First-person Camera

The lookAt function is handy when we want to fix the eye at an arbitrary location. However, if we want the viewer to be able to move around a scene, we probably want a more sophisticated abstraction that has behaviors for advancing, retreating, strafing, and turning. Let's describe in pseudocode a first-person camera class.

State

The FirstPersonCamera class tracks the following state:

All this state was described earlier in our discussion of the lookAt function. This is the state that we will need to access regularly across many methods.

Behaviors

The FirstPersonCamera class provides several behaviors for initializing the camera, moving and turning it, and building the eyeFromWorld matrix.

Constructor

The constructor receives the camera's position, the position at which it's looking, and the world's up vector. From the two positions, it computes the forward vector. The eyeFromWorld matrix must be rebuilt whenever the camera's position or directions change, so it would not be wise to build the matrix in the constructor. Instead, the matrix is built in a method named reorient, which the constructor calls.

Reorient

This method is an implementation of the lookAt function situated in the context of a class. It assumes the camera's position, forward vector, and world up vector have already been set and are accessed as state rather than parameters. It computes the camera's right and up vectors, builds the rotation and translation matrices, and multiplies the matrices together to form the eyeFromWorld matrix.

Strafe

Through the constructor we can place the camera anywhere in the world. Once the camera is positioned, we might want to move it to nearby locations. One common camera motion in games is the strafe, in which the player sidesteps to the left or right without turning. We strafe the camera by pushing its position along its right vector and then rebuilding the camera's transformation matrix, as shown in this pseudocode:

function strafe(distance)
  from = from + right * distance
  reorient camera
function strafe(distance)
  from = from + right * distance
  reorient camera

If the distance is negative, the camera moves left.

Advance

Another common motion is for the camera to advance the forward or backward. We advance the camera by pushing its position along the forward vector:

function advance(distance)
  from = from + forward * distance
  reorient camera
function advance(distance)
  from = from + forward * distance
  reorient camera

If the distance is negative, the camera moves backward.

Elevate

If the camera is jumping or traversing a bumpy terrain, its elevation will change. We elevate the camera by settings its y-component:

function elevate(elevation)
  from.y = elevation
  reorient camera
function elevate(elevation)
  from.y = elevation
  reorient camera

Rotation

Imagine we are piloting an airplane and a flight controller tells us to turn 30 degrees. This isn't a helpful command because we have many possible turns to choose from. We could yaw, which is turning left or right. We could pitch, which is turning the nose up or down. We could roll, which is turning about the nose. Or we could rotate around some non-standard axis.

Cameras are like airplanes in that they may be turned in many directions. Users typically make them yaw and pitch in order to look at nearby objects in the scene. Outside of flight simulators, rolling isn't common.

We yaw the camera by rotating the forward direction around the world's up vector, which is accomplished through a matrix-vector multiplication:

function yaw(degrees)
  forward = rotateAround(worldUp, degrees) * forward
  reorient camera
function yaw(degrees)
  forward = rotateAround(worldUp, degrees) * forward
  reorient camera

Earlier we developed routines by building matrices that rotate around the x-, y-, and z-axes. This new rotateAround function builds a matrix for rotating around an arbitrary axis. We will see how to implement it momentarily.

We pitch the camera by rotating the forward direction around the right vector:

function pitch(degrees)
  forward = rotateAround(right, degrees) * forward
  reorient camera
function pitch(degrees)
  forward = rotateAround(right, degrees) * forward
  reorient camera

If the camera pitches up or down too much, the forward vector may end up aligning with the world's up vector. This will throw off the calculation of the right vector, which is their cross product, and will likely result in an invalid eyeFromWorld matrix. For a first-person camera, the total amount of pitch should probably be clamped to a narrow range.

← LookAt MatrixRotate Around →