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:
- The camera's position
- The camera's normalized forward vector
- The camera's normalized right vector
- The world up vector
-
The
eyeFromWorld
matrix
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.