Cameras - Game Programming Algorithms and Techniques: A Platform-Agnostic Approach (2014)

Game Programming Algorithms and Techniques: A Platform-Agnostic Approach (2014)

Chapter 8. Cameras

The camera determines the player’s point of view in the 3D game world. Many different types of cameras see use in games, and selecting the type of camera is a basic design decision that typically is made early in development.

This chapter covers the major types of cameras used in games today and the calculations that must happen behind the scenes to get them to work. It also takes a deeper look at the perspective projection, which was first covered in Chapter 4, “3D Graphics.”

Types of Cameras

Before we dive into different implementations of cameras, it’s worthwhile to discuss the major types of 3D cameras used in games. This section doesn’t cover every type of camera that conceivably could be used, but it does cover many of the most common varieties.

Fixed and Non-Player Controlled Cameras

Strictly speaking, a fixed camera is one that’s in exactly the same position at all times. This type of stationary camera typically is only used in very simple 3D games. The term “fixed camera” is usually also extended to refer to systems where the camera goes to a predefined location depending on where the player character is located. As the player character moves through the level, the position of the camera will jump to new spots once the character passes a threshold. The locations and thresholds of the camera are typically set by the designer during creation of the level.

Fixed cameras were once extremely popular for survival horror games such as the original Resident Evil. Because the camera can’t be controlled by the player, it enables the game designer to set up scenarios where frightening enemies can be hidden behind corners that the camera is unable to see. This added to the atmosphere of tension that these titles were built around. A top-down view of a sample scene with a couple of fixed camera positions is shown in Figure 8.1.

Image

Figure 8.1 Top-down view of a sample scene, and where fixed cameras might be placed.

Whereas the designer might see a fixed camera as an opportunity to create tension, some players find the lack of camera control frustrating. This is perhaps one of the reasons why the modern iterations of the Resident Evil series have abandoned a fixed-camera system for more dynamic ones. Some modern games use cameras where the player still does not have any control, but the camera dynamically moves as the player moves through the level. One series that uses this type of system is God of War.

A detailed implementation of a fixed-camera system is not covered in this book, but it is one of the easier types of cameras to implement. One possible approach would be to have a list of convex polygons that correspond to a particular camera location. Then, as the player moves, the camera system could check which convex polygon the player is in (using the point in polygon algorithm covered in Chapter 7, “Physics”) and select the camera that’s associated with said convex polygon.

First-person Camera

A first-person camera is one that is designed to give the perspective of the player character moving through the world, as in Figure 8.2. Because the camera is from the character’s perspective, first-person camera is often referred to as the most immersive type of game camera. First-person cameras are very popular for the appropriately named first-person shooter, but they also see use in other games such as Skyrim.

Image

Figure 8.2 First-person camera in Quadrilateral Cowboy.

A common approach in first-person games is to place the camera roughly at eye level, so other characters and objects line up at the expected height. However, this is a problem for first-person games that want to show the hands of the player, as is common in many of them. If the camera is at eye level, when the character looks straight forward they wouldn’t see their hands. A further problem is that if a regular character model were drawn for the player, you would likely get odd graphical artifacts from the camera being nearly “inside” the model.

To solve both of these issues, most first-person games will not draw a normal character model. Instead, a special model that only has the arms (and maybe legs) is drawn at an anatomically incorrect location. This way, the player can always see what is in their hands, even though they are looking straight forward. If this approach is used, special consideration must be taken when the game allows the player to see their reflection. Otherwise, the player would see the reflection of their avatar’s hands floating in the air, which might be frightening.

Follow Cameras

A follow camera is one that follows behind a target object in one way or another. This type of camera can be used in a wide variety of genres—whether a racing game, where it follows behind a car, as in the Burnout series, or a third-person action/adventure game such as Uncharted. Because follow cameras can be used in so many different types of games, there’s a great deal of variety. A drawing of a follow camera behind a car is shown in Figure 8.3.

Image

Figure 8.3 A follow camera behind a car.

Some follow cameras very rigidly trail the target, whereas others have some amount of springiness. Some allow the player to manually rotate the camera while in follow mode, whereas others don’t. Some might even let the player instantly flip the camera to observe what’s behind them. There are many different ways this type of camera may ultimately be implemented.

Cutscene Cameras

An increasing number of games have cutscenes—that is, scenes that cut away from the actual gameplay in order to advance the story of the game in some manner. To implement a cutscene in a 3D game, at a minimum there needs to be a system to specify a series of fixed cameras while the animation plays. But most cutscenes use more cinematic devices such as panning and moving the camera, and in order to do this, some sort of spline system is typically also utilized, which we’ll discuss later in the chapter.

Perspective Projections

As discussed in Chapter 4, a perspective projection is one that has depth perception. That is to say, as objects become further and further away from the camera, they appear smaller and smaller. We briefly touched on the parameters that drive the perspective projection in Chapter 4, but now it’s time to look at this topic in more detail.

Field of View

The amount of the world that is visible at any one time is expressed as an angle known as the field of view (FOV). As humans, our pair of eyes affords us nearly a 180° field of view, though not with an equal amount of clarity. The binocular field of view, which is what can be seen with both eyes simultaneously, is approximately 120°. The rest of our field of view is in the peripheral, which is good at detecting movement but not necessarily able to effectively focus.

Field of view is an important consideration when setting up the perspective projection because an incorrect field of view can lead to eye strain, or worse, motion sickness for certain players. To see how this comes into play, let’s first take a look at the field of view for a console gamer playing on an HDTV.

The recommended viewing distance for an HDTV varies depending on who you ask, but THX recommends taking the diagonal measurement and multiplying it by 1.2. So a 50” television should be viewed from approximately 60” (or 5’). With this distance, the television has a viewing angleof approximately 40°, which means that the TV occupies 40° of the viewer’s field of view. Figure 8.4(a) demonstrates a television at the THX recommended distance.

Image

Figure 8.4 50” HDTV with a 40° viewing angle (a), and PC monitor with a 90° viewing angle (b).

In this configuration, as long as the field of view for the game is greater than or equal to 40°, all but the most sensitive of players should not have an issue viewing it. This is one of the reasons why for console games it is very common to have a field of view of approximately 65°.

But what happens if this game is instead played on a PC? In a PC configuration, the player is typically going to have the monitor occupying a much greater percentage of their binocular field of view. It is very common to have a configuration where the viewing angle of the monitor is 90° or more, as shown in Figure 8.4(b). If the game has a 65° field of view in a setup where the viewing angle is 90°, the discrepancy between the virtual field of view and the viewing angle may cause some players discomfort. That’s because the game world appears to be more narrow than what the brain is expecting to be visible in a 90° view. Because of this issue, it is a good practice for PC games to allow the player to select their preferred field of view in the settings.

If the field of view is increased, it will also increase what can be seen in the game world. Figure 8.5 illustrates a sample scene—first viewed with a 65° field of view and then viewed with a 90° field of view. Notice how in the wider field of view, more of the scene is visible. Some might argue that players who use a wider field of view will have an advantage over those using a narrower one, especially in a multiplayer game. But as long as the field of view is limited to say, 120°, I’d argue that the advantage really is fairly minimal.

Image

Figure 8.5 Sample scene shown with a 65° field of view (a) and a 90° field of view (b).

If the field of view becomes too large, it may create the fisheye effect, where the edges of the screen become distorted. This is similar to what happens when an ultra-wide angle lens is used in photography. Most games do not allow the player to select a field of view high enough to actually distort this much, but certain game mods do implement this behavior.

Aspect Ratio

The aspect ratio is the ratio between the width and height of the view into the world. For a game that runs in full screen, this will typically be the ratio between the width and height of the resolution at which the display device is running. One exception to this is a game that has a split-screen multiplayer mode; in this scenario, there are going to be multiple views into the world (or viewports), and in this instance the aspect ratio of the viewport may be different from the aspect ratio of the screen. Irrespective of this, however, the three most prevalent aspect ratios for games are 4:3, 16:9, and 16:10.

The classic 1024×768 resolution (which means it’s 1024 pixels across by 768 pixels down) is a 4:3 aspect ratio. But the number of display devices that use a 4:3 aspect ratio has decreased over time; today, almost all new displays sold are not 4:3, but rather 16:9. The standard HD resolution of 720p (which is 1280×720) is an example of a 16:9 aspect ratio, and HDTV is why 16:9 is by far the most prevalent aspect ratio in use today. 16:10 is an aspect ratio that only sees use for certain computer monitor displays, but the number of computer monitors supporting this aspect ratio is decreasing over time.

An issue that comes up when you need to support both 4:3 and 16:9 aspect ratios is determining which aspect ratio is able to see more of the game world. The preferred (and most common) solution is to have a wider horizontal field of view in 16:9 mode, which means more of the world will be visible than in 4:3. But some games, most notably BioShock at initial release, go with the opposite approach, where the 16:9 mode actually has a narrower vertical field of view than the 4:3 mode. Both approaches are shown in Figure 8.6.

Image

Figure 8.6 16:9 has a wider horizontal FOV (a) and a narrower vertical FOV (b).

Camera Implementations

Now that we’ve covered the basics of different types of cameras, as well as the basics of the perspective projection, let’s take a closer look at some of the types of camera implementations possible.

Basic Follow Camera

In a basic follow camera, the camera always follows directly behind a particular object with a prescribed vertical and horizontal follow distance. For example, a basic follow camera that’s tracking a car would follow as shown in Figure 8.7.

Image

Figure 8.7 Basic follow camera tracking a car.

Recall that to create a look-at matrix for the camera, three parameters are required: the eye (or position of the camera), the target the camera is looking at, and the camera’s up vector. In the basic follow camera, the eye is going to be a set vertical and horizontal offset from the target. Once this is calculated, we can then calculate the remaining parameters to pass to CreateLookAt:

// tPos, tUp, tForward = Position, up, and forward vector of target
// hDist = horizontal follow distance
// vDist = vertical follow distance
function BasicFollowCamera(Vector3 tPos, Vector3 tUp, Vector3 tForward,
float hDist, float vDist)
// Eye is offset from the target position
Vector3 eye = tPostForward * hDist + tUp * vDist

// Camera forward is FROM eye TO target
Vector3 cameraForward = tPos - eye
cameraForward.Normalize()

// Cross products to calculate camera left, then camera up
Vector3 cameraLeft = CrossProduct(tUp, cameraForward)
cameraLeft.Normalize()
Vector3 cameraUp = CrossProduct(cameraForward, cameraLeft)
cameraUp.Normalize()

// CreateLookAt parameters are eye, target, and up
return CreateLookAt(eye, tPos, cameraUp)
end

Although the basic follow camera will work to track the target around in the world, it will seem very rigid. There is no give to it because it always stays at the same distance from the target. When turning, the basic follow behavior makes it hard to tell whether it’s the object turning or the world turning around the object. Furthermore, it may be difficult to get a sense of speed because the distance doesn’t vary at all based on the speed the object is travelling. For all of these reasons, this basic follow camera is rarely used “as is” in games. Although it provides a simple solution, the aesthetics of it are not quite there.

One simple addition that will improve the sense of speed is to make the horizontal follow distance a function of the speed the target is travelling. So perhaps at rest the horizontal follow distance could be 100 units, but when the target is travelling at full speed, the horizontal follow distance could increase to 200. This simple change will fix the sense of speed with the basic follow camera, but won’t fix the overall stiffness.

Spring Follow Camera

With a spring follow camera, rather than the camera position instantly changing based on the orientation of the position and the target, the camera adjusts over the course of several frames. The way this is accomplished is by having both an ideal and actual camera position. The ideal camera position updates instantly every frame, and can be determined with the same calculations as the basic follow camera (perhaps along with the horizontal follow distance function). The actual camera position then lags behind a little bit and follows along the ideal position, which creates a much more fluid camera experience.

The way this is implemented is via a virtual spring connecting the ideal and the actual camera positions. When the ideal camera position changes, the spring is decompressed. If the ideal camera position stops changing, over time the spring will compress all of the way and the ideal and actual camera positions will be one and the same. This configuration with the spring, ideal, and actual camera positions is shown in Figure 8.8.

Image

Figure 8.8 A spring connects the camera’s ideal and actual positions.

The stiffness of the spring can be driven by a spring constant. The higher the constant, the stiffer the spring, which means the closer the camera stays to the ideal position. Implementing the spring follow camera requires the camera velocity and actual camera position to be persistent from frame to frame; therefore, the simplest implementation is to use a class. The algorithm roughly works by first calculating the acceleration of the spring based on the constants. Then acceleration is numerically integrated to determine the camera velocity, and then once more to determine the position. The full algorithm, as presented in Listing 8.1, is similar to the spring camera algorithm presented in Game Programming Gems 4.

Listing 8.1 Spring Camera Class


class SpringCamera
// Horizontal and vertical follow distance
float hDist, fDist
// Spring constant: higher value means stiffer spring
// A good starting value will vary based on the desired range
float springConstant
// Dampening constant is based on the above
float dampConstant

// Vectors for velocity and actual position of camera
Vector3 velocity, actualPosition

// The target game object the camera follows
// (Has position, forward vector, and up vector of target)
GameObject target

// The final camera matrix
Matrix cameraMatrix

// This helper function computes the camera matrix from
// the actual position and the target
function ComputeMatrix()
// Camera forward is FROM actual position TO target
Vector3 cameraForward = target.position - actualPosition
cameraForward.Normalize()

// Cross to calculate camera left, then camera up
Vector3 cameraLeft = CrossProduct(target.up, cameraForward)
cameraLeft.Normalize()
Vector3 cameraUp = CrossProduct(cameraForward, cameraLeft)
cameraUp.Normalize()

// CreateLookAt parameters are eye, target, and up
cameraMatrix = CreateLookAt(actualPosition, target.position,
cameraUp)
end

// Initializes the constants and camera to initial orientation
function Initialize(GameObject myTarget, float mySpringConstant,
float myHDist, float myVDist)
target = myTarget
springConstant = mySpringConstant
hDist = myHDist
vDist = myVDist

// Dampening constant is calculated from spring constant
dampConstant = 2.0f * sqrt(springConstant)

// Initially, set the actual position to the ideal position.
// This is like the basic follow camera eye location.
actualPosition = target.positiontarget.forward * hDist +
target.up * vDist

// The initial camera velocity should be all zeroes
velocity = Vector3.Zero

// Set the initial camera matrix
ComputeMatrix()
end

function Update(float deltaTime)
// First calculate the ideal position
Vector3 idealPosition = target.positiontarget.forward * hDist
+ target.up * vDist

// Calculate the vector FROM ideal TO actual
Vector3 displacement = actualPositionidealPosition
// Compute the acceleration of the spring, and then integrate
Vector3 springAccel = (-springConstant * displacement) –
(dampConstant * velocity)
velocity += springAccel * deltaTime
actualPosition += velocity * deltaTime

// Update camera matrix
ComputeMatrix()
end
end


The biggest advantage of a spring camera is that when the target object turns, the camera will have a bit of a delay before it starts turning as well. This results in a sense of turning that’s much more aesthetically pleasing than a basic follow camera. And even though the spring camera looks much better than the basic follow camera, it does not require too many additional calculations.

Orbit Camera

An orbit camera is one that orbits around a target object. The simplest way to implement orbiting is to store the camera’s position as an offset from the target, as opposed to storing the world space position of the camera. That’s due to the fact that rotations are always applied with the assumption that the rotation is about the origin (which hopefully you recall from Chapter 4). So if the camera position is stored as an offset, any rotations applied will act as if the target object is at the origin, which in the case of orbiting is precisely what we want. This type of orbit camera is demonstrated in Figure 8.9.

Image

Figure 8.9 Orbit camera.

The typical control scheme for orbiting enables the player to both yaw and pitch, but not roll. Because the input scheme will typically provide the yaw and pitch as separate values, it’s best to implement the orbiting as a two-part rotation. First, rotate the camera up and then offset about the world up axis (for yaw), then rotate the camera up and offset about the camera’s left vector (for pitch). You need to rotate the camera up in both rotations in order to get the correct behavior when the camera is below the target.

As for how the rotation should be implemented, the system presented in Listing 8.2 uses quaternions, though it would be possible to implement it with matrices instead. I should also note that there is an entirely different approach to implementing an orbit camera using spherical coordinates, but I find the approach presented here a bit easier to follow.

Listing 8.2 Orbit Camera


class OrbitCamera
// Up vector for camera
Vector3 up
// Offset from target
Vector3 offset
// Target object
GameObject target
// Final camera matrix
Matrix cameraMatrix

// Initializes the initial camera state
function Initialize(GameObject myTarget, Vector3 myOffset)
// In a y-up world, the up vector can just be the Y axis
up = Vector3(0,1,0)

offset = myOffset
target = myTarget

// CreateLookAt parameters are eye, target, and up
cameraMatrix = CreateLookAt(target.position + offset,
target.position, up)
end

// Updates based on the incremental yaw/pitch angles for this frame
function Update(float yaw, float pitch)
// Create a quaternion that rotates about the world up
Quaternion quatYaw = CreateFromAxisAngle(Vector3(0,1,0), yaw)
// Transform the camera offset and up by this quaternion
offset = Transform(offset, quatYaw)
up = Transform(up, quatYaw)

// The forward is target.position - (target.position + offset)
// Which is just -offset
Vector3 forward = -offset
forward.Normalize()
Vector3 left = CrossProduct(up, forward)
left.Normalize()

// Create quaternion that rotates about camera left
Quaternion quatPitch = CreateFromAxisAngle(left, pitch)
// Transform camera offset and up by this quaternion
offset = Transform(offset, quatPitch)
up = Transform(up, quatPitch)

// Now compute the matrix
cameraMatrix = CreateLookAt(target.position + offset,
target.position, up)
end
end


In some games, it may be desirable to have the spring camera behavior as well as the ability to manually orbit the camera. Both methods can certainly be combined to enable following and orbiting, but the implementation of this is left as an exercise for the reader.

First-person Camera

With a first-person camera, the position of the camera is always a set vertical offset from the player character’s position. So as the player moves through the world, the camera remains at the player position plus the vertical offset. But even though the camera offset doesn’t change, the target location can change independently. That’s because most first-person games support looking around and turning while not changing the physical location of the character. This configuration is shown in Figure 8.10.

Image

Figure 8.10 First-person camera implementation.

The implementation for the rotation ends up being similar to the rotation of the orbit camera; the main difference is that it is the target offset that’s being rotated instead of a camera offset. But there are some additional differences with this type of camera. One is that when the target offset is rotated about the world up, the avatar must also be rotated in the same manner. Another change is that typically the pitch angle is limited to be no more than a certain value.

Due to these modifications, for a first-person camera it’s common to store the pitch and yaw as the total angles, as opposed to the delta amount per frame. This means that the rotation will always be applied to the initial target offset, as opposed to an incremental rotation, as was done with the orbit camera. An implementation of a first-person camera is provided in Listing 8.3.

Listing 8.3 First-person Camera


class FirstPersonCamera
// Camera offset from player character position
// For a y-up world, the vector would be (0, value, 0)
Vector3 verticalOffset
// Target position offset from camera
// For a z-forward world, vector would be (0, 0, value)
Vector3 targetOffset
// Total yaw and pitch angles
float totalYaw, totalPitch
// Player that the camera is attached to
GameObject Player
// Actual camera matrix
Matrix cameraMatrix

// Initializes all the camera parameters
function Initialize(GameObject myPlayer, Vector3 myVerticalOffset,
Vector3 myTargetOffset)
player = myPlayer
verticalOffset = myVerticalOffset
targetOffset = myTargetOffset

// Initially, there's no extra yaw or pitch
totalYaw = 0
totalPitch = 0

// Calculate camera matrix
Vector3 eye = player.position + verticalOffset
Vector3 target = eye + targetOffset
// For y-up world
Vector3 up = Vector3(0, 1, 0)
cameraMatrix = CreateLookAt(eye, target, up)
end

// Updates based on the incremental yaw/pitch angles for this frame
function Update(float yaw, float pitch)
totalYaw += yaw
totalPitch += pitch

// Clamp the total pitch based on restrictions
// In this case, 45 degrees (approx 0.78 rad)
totalPitch = Clamp(totalPitch, -0.78, 0.78)

// The target offset is before any rotations,
// actual is after rotations.
Vector3 actualOffset = targetOffset

// Rotate actual offset about y-up for yaw
Quaternion quatYaw = CreateFromAxisAngle( Vector3(0,1,0),
totalYaw)
actualOffset = Transform(actualOffset, quatYaw)

// Calculate left for pitch
// Forward after yaw just the actualOffset (normalized)
Vector3 forward = actualOffset
forward.Normalize()
Vector3 left = CrossProduct(Vector3(0, 1, 0), forward)
left.Normalize()

// Rotate actual offset about left for pitch
Quaternion quatPitch = CreateFromAxisAngle(left, totalPitch)
actualOffset = Transform(acutalOffset, quatPitch)

// Now construct the camera matrix
Vector3 eye = player.position + verticalOffset
Vector3 target = eye + actualOffset
// In this case we can just pass in world up, since we can never
// rotate upside-down.
cameraMatrix = CreateLookAt(eye, target, Vector3(0, 1, 0))
end
end


Spline Camera

Though the mathematical definition is a bit more specific, a spline can be thought of as a curve that is defined by a series of points that lie on the curve. Splines are popular in games because they enable an object to smoothly interpolate across the curve over time. This can be very useful for cutscene cameras, because it means it is possible to have a camera follow along a predefined spline path.

There are many different types of splines, but one of the simplest is the Catmull-Rom spline. This type of spline enables interpolation between two adjacent points provided there is a control point before and after the two active ones. For example, in Figure 8.11, P1 and P2 are the active control points (at t = 0 and t = 1, respectively) and P0 and P3 are the control points prior to and after. Even though the figure shows only four points, there is no practical limit. As long as there is a control point before and after, the path can go on indefinitely.

Image

Figure 8.11 Catmull-Rom spline with the minimum four points.

Given this set of four control points, it is then possible to compute the position on the spline at any t value between 0 and 1 using the following equation:

Image

Note that this equation only works if there is uniform spacing between the control points. In any event, with this equation in hand, a simple Catmull-Rom spline class that supports an arbitrary number of points could be implemented as follows:

class CRSpline
// Vector (dynamic array) of Vector3s
Vector controlPoints

// First parameter is the control point that corresponds to t=0
// Second parameter is the t value
function Compute(int start, float t)
// Check that start – 1, start, start + 1, and start + 2
// all exist
...

Vector3 P0 = controlPoints[start – 1]
Vector3 P1 = controlPoints[start]
Vector3 P2 = controlPoints[start + 1]
Vector3 P3 = controlPoints[start + 2]

// Use Catmull-Rom equation to compute position
Vector3 position = 0.5*((2*P1)+(-P0+P2)*t+
(2*P0-5*P1+4*P2-P3)*t*t+
(-P0+3*P1-3*P2+P3)*t*t*t)

return position
end
end

It is also is possible to compute the tangent at any t between 0 and 1 using the same equation. First, compute the position at t like you normally would. Then, compute the position at t plus a small Δt. Once you have both positions, you can then construct a vector from P(t) to P(t + Δt) and normalize it, which will approximate the tangent vector. In a way, this approach can be thought of as numeric differentiation.

If a camera is following the spline, the tangent vector corresponds to the facing vector of the camera. Provided that the spline does not allow the camera to be flipped upside down, once you have both the position and the facing vector, the camera matrix can be constructed. This is shown with the spline camera implementation in Listing 8.4.

Listing 8.4 Spline Camera


class SplineCamera
// Spline path the camera follows
CRSpline path
// Current control point index and t value
int index
float t
// speed is how much t changes per second
float speed
// Actual camera matrix
Matrix cameraMatrix

// Calculates the camera matrix given current index and t
function ComputeMatrix()
// Eye position is the spline at the current t/index
Vector3 eye = path.Compute(index, t)
// Get point a little bit further ahead to get the target point
Vector3 target = path.Compute(index, t + 0.05f)
// For y-up world
Vector3 up = Vector3(0, 1, 0)

cameraMatrix = CreateLookAt(eye, target, up)
end

function Initialize(float mySpeed)
// The initial index should be 1 (because 0 is P0 initially)
index = 1
t = 0.0f
speed = mySpeed

ComputeMatrix()
end

function Update(float deltaTime)
t += speed * deltaTime

// If t > = 1.0f, we should move to the next control point
// This code assumes that the speed won't be so fast that we
// might move forward two control points in one frame.
if t >= 1.0f
index++
t = t – 1.0f
end

// Should check that index+1 and index+2 are valid points
// If not, this spline path is complete
...

ComputeMatrix()
end
end


Camera Support Algorithms

The implementations covered in the preceding section should provide enough information to get a camera that works on a basic level. But basic operation is only a small part of implementing a successful camera. In order to implement a camera that is useful for the player, additional support algorithms should be considered.

Camera Collision

Camera collision strives to solve a very common problem with many types of cameras: when there is an opaque object between the camera and the target. The simplest (though not the best) way to fix this is by performing a ray cast from the target position to the camera position. If the ray cast collides with an object, the camera can be moved to a position that’s in front of the object that’s blocking the camera, as shown in Figure 8.12. Unfortunately, this behavior can be jarring; a more robust solution is to make a physics object representing the camera, but that’s beyond the scope of this section.

Image

Figure 8.12 Camera blocked by an object (a), and camera moved up so it’s no longer blocked (b).

Another consideration is what should be done when the camera becomes too close to the target object. Recall that the near plane of the camera is a little bit in front of the eye, which means a close camera could cause part of the target object to disappear. A popular solution to this problem is to hide or alpha out the target if the camera gets too close.

The alpha solution sometimes is also applied to the general problem of the camera being blocked by an object. So rather than moving the camera forward, the game may just alpha out the object that’s blocking it. Several third-person action games utilize this method.

Picking

Picking is the capability to click or tap to select an object in the 3D world. Picking is commonly used in RTS games, such as the tower defense game in Chapter 14, “Sample Game: Tower Defense for PC/Mac.” In that particular game, picking is implemented in order to enable selection of the various towers (and locations where towers can be placed) in the game world. Although picking is not strictly a camera algorithm, it does tie in with the camera and projection.

Recall that to transform a given point in world space into projection (or screen) space, it must be multiplied by the camera matrix followed by the projection matrix. However, the position of the mouse (or touch) is expressed as a 2D point in screen space. What we need to do is take that 2D point that’s in screen space and transform it back into world space, which is known as an unprojection. In order to perform an unprojection, we need a matrix that can do the opposite transformation. For a row-major representation, this means we need the inverse of the camera matrix multiplied by the projection matrix:

unprojection = (camera × projection)–1

But a 2D point can’t be multiplied by a 4×4 matrix, so before the point can be multiplied by this matrix, it must be converted to homogenous coordinates. This requires z- and w-components to be added to the 2D point. The z-component is usually set to either 0 or 1, depending on whether the 2D point should be converted to a point on the near plane or the far plane, respectively. And since it’s a point, the w-component should always be 1. The following Unproject function takes a 4D vector, because it assumes that it already has its z- and w-components set appropriately:

function Unproject(Vector4 screenPoint, Matrix camera, Matrix projection)
// Compute inverse of camera * projection
Matrix unprojection = camera * projection
unprojection.Invert()

return Transform(screenPoint, unprojection)
end

The Unproject function can then be used to calculate two points: the mouse position unprojected onto the near plane (z = 0) and the mouse position unprojected onto the far plane (z = 1). These two points in world space can then be used as the start and end points of a ray cast that tests against all of the selectable objects in the world. Because there can potentially be multiple objects the ray cast intersects with, the game should select the closest one. Figure 8.13 shows the basic premise behind picking.

Image

Figure 8.13 Picking casts a ray from the near to far plane and selects an object that intersects with the line segment.

Summary

The camera is a core component of any 3D game. You potentially might use lots of different types of cameras, and this chapter covered implementations of many of them. A first-person camera gives an immersive view from the eye of a character. A follow camera might be used to chase behind a car or a player. An orbiting camera rotates around a specific target, and a spline camera might be used for cutscenes. Finally, many games might need to implement picking in order to allow the player to click on and select objects.

Review Questions

1. What does field of view represent? What issues can arise out of a narrow field of view?

2. How can you set up a basic camera that follows a target at a set horizontal and vertical distance?

3. In what way does a spring follow camera improve upon a basic follow camera?

4. When implementing an orbit camera, how should the camera’s position be stored?

5. How does a first-person camera keep track of the target position?

6. What is a Catmull-Rom spline?

7. What is a spline camera useful for?

8. A follow camera is consistently being blocked by an object between the camera and the target. What are two different ways this can be solved?

9. In an unprojection, what do z-components of 0 and 1 correspond to?

10. How can unprojection be used to implement picking?

Additional References

Haigh-Hutchinson, Mark. Real-Time Cameras. Burlington: Morgan Kaufmann, 2009. This book provides a comprehensive look at many different types of game cameras, and was written by the creator of the excellent camera systems in Metroid Prime.