Third-person Camera
A first-person camera gives a first-person perspective of the virtual world. The avatar and eye are one and the same. Sometimes we want to see the avatar, especially in games in which the primary mechanic isn't firing projectiles at enemies from afar but rather melee combat or athletic maneuvers. On these occasions we need a third-person camera. Like the one in this renderer, which can be moved with the mouse and WASD keys:
Let's examine what a third-person camera abstraction might look like. In the following discussion, we assume that the third-person camera is associated with an avatar model that is defined in model space to reside at the origin, stand upright, and look along the negative z-axis.
State
The ThirdPersonCamera
maintains the following state in order to orient itself and the avatar to which it is attached:
- The avatar's world space position, which we'll call the anchor.
- The avatar's forward vector, which is the direction in which the avatar is looking.
- The avatar's focal distance, a scalar that measures how far ahead the avatar is looking along its forward vector. This wasn't needed with the first-person camera, but it is here because we need to have the camera look at the same thing as the avatar.
- The avatar's right vector.
- The world's up vector, which we'll assume to always be \(\begin{bmatrix}0&1&0\end{bmatrix}\).
- The viewer's position behind the avatar in model space. A viewer one unit directly behind and one unit up from the avatar will have a position of \(\begin{bmatrix}0&1&1\end{bmatrix}\).
-
The
eyeFromWorld
matrix. -
The avatar's
worldFromModel
matrix.
A first-person camera positions and orients just a camera. In contrast, a third-person camera positions and orients the avatar—which is visible—and the camera is situated behind at some distance. When the avatar moves or turns, the camera tags along. The abstraction therefore maintains two matrices. The worldFromModel
matrix is used to transform the avatar model. This matrix applies only to the avatar; other models in the scene will have their own worldFromModel
matrices not maintained by the camera. The eyeFromWorld
matrix puts the viewer looking over the avatar's shoulder.
Behaviors
The ThirdPersonCamera
class provides several behaviors for initializing the camera, moving and turning it, and building its matrices.
Constructor
The constructor receives the avatar's position, the position at which it's looking, and the viewer's position in the avatar's model space. From the two positions, it computes the avatar's forward vector and focal distance.
The matrices must be rebuilt whenever the avatar's position or orientation changes, so it would not be wise to build the matrix in the constructor. Instead, the matrices are built in a method named reorient
, which the constructor calls.
Reorient
The reorient
method is responsible for assembling the eyeFromWorld
and worldFromModel
matrices whenever the avatar is moved. Let's examine each of these matrices in turn.
We want to rotate the avatar so that it is looking in the desired direction, so we build a rotation matrix out of the avatar's three world space axes. We have the avatar's forward vector as part of the camera state, and we compute the other two vectors using cross products:
function reorient()
// Find the avatar's axes
avatarRight = cross avatarForward with worldUp vector
normalize avatarRight
avatarUp = cross avatarRight with avatarForward
...
function reorient() // Find the avatar's axes avatarRight = cross avatarForward with worldUp vector normalize avatarRight avatarUp = cross avatarRight with avatarForward ...
Earlier we formed the rotation matrix of a first-person camera by dropping into its rows the axes of the incoming world space that were to become the x-, y-, and z-axes of the outgoing eye space. That same trick won't work here—not exactly. The vectors we just computed are the outgoing world space vectors that we want the incoming model space x-, y-, and z-axes to become. That's the reverse of the situation we had with the first-person camera.
There's a related law of rotation matrices that can help us out: the columns of a rotation matrix represent the outgoing vectors that the x-, y-, and z-axes of the incoming space become. For example, this rotation matrix makes the avatar's right arm, which points along the x-axis in model space, point along the world space vector \(\mathrm{right}\):
Altogether, this matrix rotates our avatar into the desired orientation:
We also need to translate the avatar from its origin in model space to its position in world space. Together the translation and rotation matrices form the avatar's worldFromModel
matrix:
function reorient()
...
// Build worldFromModel
rotateAvatar = matrix whose columns are avatar's right,
up, and negative forward vectors
translateAvatar = matrix that translates avatar to
position
worldFromModel = translateAvatar * rotateAvatar
...
function reorient() ... // Build worldFromModel rotateAvatar = matrix whose columns are avatar's right, up, and negative forward vectors translateAvatar = matrix that translates avatar to position worldFromModel = translateAvatar * rotateAvatar ...
Next up is the eyeFromWorld
matrix. It is assembled in much the same way as the first-person camera's matrix. But this time the camera's position is derived from the avatar. We compute it by transforming the camera's offset by the worldFromEye
matrix:
function reorient()
...
// Compute camera properties
cameraFrom = worldFromEye * offset
...
function reorient() ... // Compute camera properties cameraFrom = worldFromEye * offset ...
Next we determine the camera's right, up, and forward vectors:
function reorient()
...
focalPoint = anchor + forward * focalDistance
cameraForward = normalize focalPoint - cameraFrom
cameraRight = cross cameraForward and worldUp
normalize cameraRight
cameraUp = cross cameraRight with cameraForward
...
function reorient() ... focalPoint = anchor + forward * focalDistance cameraForward = normalize focalPoint - cameraFrom cameraRight = cross cameraForward and worldUp normalize cameraRight cameraUp = cross cameraRight with cameraForward ...
These vectors for the rows of the eyeFromWorld
matrix, just as they did in the first-person camera:
function reorient()
...
// Build eyeFromWorld
translateCamera = matrix that translates camera to origin
rotateCamera = matrix whose rows are camera's right,
up, and negative forward vectors
eyeFromWorld = rotateCamera * translateCamera
function reorient() ... // Build eyeFromWorld translateCamera = matrix that translates camera to origin rotateCamera = matrix whose rows are camera's right, up, and negative forward vectors eyeFromWorld = rotateCamera * translateCamera
Strafe
As with a first-person camera, we have the avatar strafe by pushing its position along its right vector and then rebuilding the transformation matrices, as shown in this pseudocode:
function strafe(distance)
anchor = anchor + avatarRight * distance
reorient camera
function strafe(distance) anchor = anchor + avatarRight * distance reorient camera
Since the camera is positioned by a relative offset from the avatar, moving the avatar also moves the camera.
Advance
To make the avatar walk forward or backward, we move it along the forward vector:
function advance(distance)
anchor = anchor + avatarForward * distance
reorient camera
function advance(distance) anchor = anchor + avatarForward * distance reorient camera
Rotation
Since a third-person camera has a more expansive view than a first-person camera, we'll omit pitching and support only yawing. As with the first-person camera, we implement yawing by rotating the avatar's forward around the world's up axis:
function yaw(degrees)
forward = rotateAround(worldUp, degrees) * forward
reorient camera
function yaw(degrees) forward = rotateAround(worldUp, degrees) * forward reorient camera