|
Features

Animation With Cg
Key-Frame
Interpolation
3D
games often use a sequence of key frames to represent an animated human
or creature in various poses. For example, a creature may have animation
sequences for standing, running, kneeling, ducking, attacking, and dying.
Artists call each particular pose that they create for a given 3D model
a key frame.
Key-Framing
Background
The
term key frame comes from cartoon animation. To produce a cartoon,
an artist first quickly sketches a rough sequence of frames for animating
a character. Rather than draw every frame required for the final animation,
the artist draws only the important, or "key," frames. Later,
the artist goes back and fills in the missing frames. These in-between
frames are then easier to draw, because the prior and subsequent key frames
serve as before-and-after references.
Computer
animators use a similar technique. A 3D artist makes a key frame for each
pose of an animated character. Even a standing character may require a
sequence of key frames that show the character shifting weight from one
foot to the other. Every key frame for a model must use the exact same
number of vertices, and every key frame must share the same vertex connectivity.
A vertex used in a given key frame corresponds to the same point on the
model in every other key frame of the model. The entire animation sequence
maintains this correspondence. However, the position of a particular vertex
may change from frame to frame, due to the model's animation.
Given
such a key-framed model, a game animates the model by picking two key
frames and then blending together each corresponding pair of vertex positions.
The blend is a weighted average in which the sum of the weights equals
100 percent. Figure 6-7 shows an alien character with several key frames.
The figure includes two key frames, marked A and B, to be blended into
an intermediate pose by a Cg program.
An
application can use a Cg vertex program to blend the two vertices together.
This blending may include further operations to illuminate the blended
character appropriately for more realism. Usually, an application specifies
a single position for each vertex, but for key-frame blending, each vertex
has two positions, which are combined with a uniform weighting factor.
Key-frame
interpolation assumes that the number and order of vertices are the same
in all the key frames for a given model. This assumption ensures that
the vertex program is always blending the correct pairs of vertices. The
following Cg code fragment blends key-frame positions:
blendedPosition
= (1 - weight) * keyFrameA + weight * keyFrameB; 157
The
keyFrameA and keyFrameB
variables contain the (x, y, z) positions of the vertex being processed
at key frames A and B, respectively. Note that weight
and (1 - weight) sum to
1. If weight is 0.53, the Cg program adds 47 percent (1.0 - 0.53) of the
position of key frame A to 53 percent of the position of key frame B.
Figure 6-8 shows an example of this type of animation.
To
maintain the appearance of continuous animation, the key-frame weight
increases with each rendered frame until it reaches 1.0 (100 percent).
At this point, the existing key frame B becomes the new key frame A, the
weight resets to 0, and the game selects a new key frame B to continue
the animation. Animation sequences, such as walking, may loop repeatedly
over the set of key frames that define the character's walking motion.
The game can switch from a walking animation to a running animation just
by changing over to the sequence of key frames that define the running
animation.
It
is up to the game engine to use the key frames to generate convincing
animated motion. Many existing 3D games use this style of key-frame animation.
When an application uses a Cg vertex program to perform the key-frame
blending operations, the CPU can spend time improving the gameplay rather
than continuously blending key frames. By using a Cg vertex program, the
GPU takes over the task of key-frame blending.
Interpolation
Approaches
There
are many types of interpolation. Two common forms for key-frame interpolation
are linear interpolation and quadratic interpolation.
Linear
Interpolation
With
linear interpolation, the transition between positions happens at a constant
rate. Equation 6-2 shows the definition of linear interpolation:
|
|
blendedPosition
= positionA x (1-f) + positionB x f
|
 |
 |
 |
Equation 6-2. Linear Interpolation
|
As
f varies from 0 to 1 in this equation, the intermediate position
varies between positionA and positionB. When f is
equal to 0, the intermediate position is exactly positionA, the
starting position. When f is equal to 1, the intermediate position
is positionB, the ending position. Once again, you can use Cg's
lerp function to accomplish
the interpolation.
Using
lerp, the interpolation
between two positions can be written concisely as:
intermediatePosition
= lerp(positionA, positionB, f);
Quadratic
Interpolation
Linear
interpolation is good for many situations, but sometimes you want the
rate of transition to change over time. For example, you might want the
transition from positionA to positionB to start out slowly
and get faster as time passes. For this, you might use quadratic interpolation,
as in the following code fragment:
intermediatePosition
= position1 * (1 - f * f) + position2 * f * f
Other
functions that you might use are step functions, spline functions, and
exponential functions. Figure 6-9 shows several common types of interpolation
functions.
Basic
Key-Frame Interpolation
Example
6-3 shows the C6E3v_keyFrame
vertex program. This program performs the object-space blending of two
positions, each from a different key frame. The lerp
Standard Library function linearly interpolates the two positions,
and then the program transforms the blended position into clip space.
The program passes through a texture coordinate set and a color.
As
indicated by the input semantics for positionA
and positionB, the application
is responsible for configuring key frame A's position as the conventional
position (POSITION) and
key frame B's position as texture coordinate set 1 (TEXCOORD1).
|
|
| void
C6E3v_keyFrame( |
float3
positionA : POSITION,
float3 positionB : TEXCOORD1,
float4 color : COLOR,
float2 texCoord : TEXCOORD0, |
out
float4 oPosition : POSITION,
out float2
oTexCoord : TEXCOORD0,
out float4
oColor : COLOR,
uniform float keyFrameBlend,
uniform float4x4 modelViewProj)
{
float3 position = lerp(positionA, positionB,
keyFrameBlend);
oPosition = mul(modelViewProj, float4(position,
1));
oTexCoord = texCoord;
oColor = color;
}
|
 |
 |
 |
Example
6-3. The C6E3v_keyFrame
Vertex Program
|
The
application is also responsible for determining the key-frame blending
factor via the uniform parameter keyFrameBlend.
The value of keyFrameBlend
should transition from 0 to 1. Once 1 is reached, the application chooses
another key frame in the animation sequence, the old key frame B position
input is then configured as the key frame A position input, and the new
key-frame position data feeds the key frame B position input.
Key-Frame
Interpolation with Lighting
You
often want to light a key-framed model. This involves not merely blending
two positions (the vertex in two different key frames), but also blending
the two corresponding surface normals. Then you can calculate lighting
computations with the blended normal. Blending two normals may change
the length of the resulting normal, so you must normalize the blended
normal prior to lighting.
Example
6-4 shows the C6E4v_litKeyFrame
vertex program that adds per-vertex
|
|
struct Light {
float3 eyePosition; // In object space
float3 lightPosition; // In object space
float4 lightColor;
float specularExponent;
float ambient;
};
| float4
computeLighting( |
float4
computeLighting(Light light,
float3 position, // In object space
float3 normal) // In object space |
{
float3 lightDirection = light.lightPosition
- position;
float3 lightDirNorm = normalize(lightDirection);
float3 eyeDirection = light.eyePosition - position;
float3 eyeDirNorm = normalize(eyeDirection);
float3 halfAngle = normalize(lightDirNorm
+ eyeDirNorm);
float diffuse = max(0, dot(lightDirNorm,
normal));
float specular = pow(max(0, dot(halfAngle,
normal)),
light.specularExponent);
return light.lightColor * (light.ambient +
diffuse + specular);
}
void C6E4v_litKeyFrame(float3 positionA : POSITION,
float3
normalA : NORMAL,
float3
positionB : TEXCOORD1,
float3
normalB : TEXCOORD2,
float2
texCoord: TEXCOORD0,
out float4
oPosition : POSITION,
out float2
oTexCoord : TEXCOORD0,
out float4
color : COLOR,
uniform
float keyFrameBlend,
uniform Light light,
uniform float4x4 modelViewProj)
{
float3 position = lerp(positionA, positionB,
keyFrameBlend);
float3 blendNormal = lerp(normalA, normalB,
keyFrameBlend);
float3 normal = normalize(blendNormal);
oPosition = mul(modelViewProj, float4(position,
1));
oTexCoord = texCoord;
color = computeLighting(light, position, normal);
}
|
 |
 |
 |
Example
6-4. The C6E4v_litKeyFrame Vertex Program
|
lighting
to the C6E3v_keyFrame example.
In the updated example, each key frame also supplies its own corresponding
per-vertex surface normal.
The
computeLighting internal function computes a conventional lighting model
using object-space lighting.
______________________________________________________
|