Lecture 10: Animation 1 (Curves) (slides)

Learning Objectives

By the end of this lecture, you will be able to:

  • specify scene parameters as keyframes in an animation,
  • interpolate points to define a smooth curve,
  • construct cubic splines from control points,
  • represent cubic splines using a Hermite basis and a Bézier basis,
  • apply de Casteljau's algorithm to evaluate and render a cubic Bézier curve.

In 1987, John Lasseter wrote a paper called Principles of Traditional Animation Applied to 3D Computer Animation, which describes the principles that were traditionally used in the early 1930's for producing 2D animations and how they should be applied to modern 3D animation. The 11 principles are:

  1. Squash and stretch: defining the rigidity and mass of an object by distorting its shape during an action.
  2. Timing and motion: spacing actions to define the weights and size of objects and the personality of characters.
  3. Anticipation: the preparation for an action (like retracting a foot, raising arms, etc.).
  4. Staging: presenting an idea so that it is unmistakably clear.
  5. Follow through and overlapping action: the termination of an action and establishing its relationship to the next action.
  6. Straight-ahead action and pose-to-pose action: the two contrasting approaches to the creation of movement. Straight-ahead action is the idea of drawing one frame at a time, starting with the initial frame, to create motion. Pose-to-pose action involves defining certain frames with different poses, and then drawing the in-between frames.
  7. Slow in and out: the spacing of the in-between frames to achieve subtlety of timing and movement. Physically, this has to do with the acceleration (rate-of-change of velocity) of the path describing the motion.
  8. Arcs: the visual path of action of an object resulting from another action.
  9. Exaggeration: accentuating the essence of an idea via the design and the action.
  10. Secondary action: the action of an object resulting from another action.
  11. Appeal: creating a design or an action that the audience enjoys watching.

Think about these principles as you watch the 1986 Pixar short called Luxo Jr. The leftmost image below (from Lasseter's paper) shows the concept of squash and stretch (Principle 1) whereas the image on the right shows different spacing between frames (Principle 7).

Animation is not necessarily about simulating what actually happens in the physical world (sometimes it is) - it's about telling a story. Based on the story an animator is trying to tell, certain special effects might be required. The role of the computer graphics developer is then to produce software that an animator can use to achieve these effects with relatively few knobs to turn.

Keyframing

For pose-to-pose action (Principle 6), an animation artist may wish to specify scene parameters (e.g. object positions) at certain frames and then let an algorithm determine the scene parameters at the "in-between" frames. This process, known as keyframing, consists of specifying keys $(t_k, f_k)$. Each key $(t_k, f_k)$ consists of a time $t_k$ and a scene parameter $f_k$ (for example, $f_k$ might be an object's position). These keys can then be interpolated to determine a function for how the scene parameters change at frames in between the keys. How this interpolation is done affects the motion of the objects which, in turn, affects how the story is told.

First attempt: linear interpolation

Suppose we want to animate the vertical position of ball as a function of time. In pose-to-pose animation, we specify the vertical position ($y$) of the ball at specific times and then interpolate the position at intermediate times. Let's start with linear interpolation: the vertical position $y$ is a linear function of time ($t$) in between the different poses. For two poses $(t_j, y_j)$ and $(t_{j+1}, y_{j+1})$, the height of the ball $y(t)$ in between these two poses would be:

$$ y(t) = y_j + \frac{y_{j+1} - y_j}{t_{j+1} - t_j} t, \quad t \in [t_j, t_{j+1}]. $$

In the demo below, you can click on the canvas to add new poses at the clicked location. You can also hover over an existing point, then click and drag to move it. Then click the animate button using the linear option in the dropdown.


(degree)

As you can see, the result looks pretty choppy with linear interpolation - can we do better?

Second attempt: global high-order interpolation.

To make this look better, we can use a smooth curve. Since we want the ball to go through various positions, one idea might be to interpolate all the keys in our animation globally - that is we have a single curve that goes through all the points. For $n$ keys, we can construct an interpolating curve with degree $n - 1$. For example, let's try to do this for four keys ($n = 4$). With 4 keys, we can use a cubic curve which can be represented parametrically using:

$$ y(t) = c_0 + c_1 t + c_2 t^2 + c_3 t^3 $$

for some unknown constants $c_0$, $c_1$, $c_2$ and $c_3$. The 4 keys specified by the animation artist will allow us to solve for the four constants $c_i$ ($0 \le i \lt 4$):

$$ \vec{y} = \left[\begin{array}{c} y_0 \\ y_1 \\ y_2 \\ y_3 \end{array}\right] = \left[ \begin{array}{cccc} 1 & t_0 & t_0^2 & t_0^3 \\ 1 & t_1 & t_1^2 & t_1^3 \\ 1 & t_2 & t_2^2 & t_2^3 \\ 1 & t_3 & t_3^2 & t_3^3 \\ \end{array} \right] \left[\begin{array}{c} c_0 \\ c_1 \\ c_2 \\ c_3 \end{array}\right] = \mathbf{V}\vec{c}, \quad \rightarrow \quad \vec{c} = \mathbf{V}^{-1} \vec{y}. $$

Click the canvas above to add a fourth point (maybe refresh first), and select the interpolate dropdown option.

While this method gives us a smooth curve that goes through all the points, things get bad when we have a lot of points. Try adding several additional keys to our poses. The curve starts to oscillate a lot which demonstrates something known as the Runge phenomenon.

Approximating the curve (using the approximate dropdown option) makes the curve smoother, but now the curve (and hence the motion of the ball) no longer goes through the keys we specified. This is also not a good option. This idea is also shown in the image below (from Lasseter's paper).

Third attempt: splines.

Instead of creating a global interpolating curve of all the points, we can define a single smooth curve in between two keys and connect each of the curve segments to produce the full curve, which is known as a spline. In general, we will use cubic splines, meaning the curve segment in between two keys will be a cubic polynomial like we had above.

There is still a problem with this idea: we only know two points on the curve. To define a cubic polynomial, we need four points (keys) to determine all four $c_i$ coefficients. Instead of specifying additional points on the curve, we can specify the derivatives of the curve at the end points. This is useful because we can use that to control how the animation transitions from one segment to the next. Maybe we want the motion to be smooth - in this case, the derivatives at the endpoints of adjacent splines should match.

Let's go back to our curve equation, and also express the derivative with respect to $t$, denoted by $y^\prime(t)$:

$$ y(t) = c_0 + c_1 t + c_2 t^2 + c_3 t^3, \quad y^\prime(t) = c_1 + 2 c_2 t + 3 c_3 t^2, \quad t \in [t_{i}, t_{i+1}] $$

which is defined over the $t$ interval for the two keys at $i$ and $i+1$.

Hermite basis.

In the Hermite basis representation, we want to use the values $y_{i}$ and $y_{i+1}$ as well as the derivatives $y^\prime_{i}$ and $y^\prime_{i+1}$. We will parametrize our polynomials over the interval $[0,1]$. Therefore,

$$ \begin{array}{rl} y_i =& c_0 \\ y^\prime_i =& c_1 \\ y_{i+1} =& c_0 + c_1 + c_2 + c_3 \\ y^\prime_{i+1} =& c_1 + 2c_2 + 3c_3. \end{array} $$

This is a system of four equations and four unknowns, which we can set up as

$$ \left[\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 1 & 1 & 1 & 1 \\ 0 & 1 & 2 & 3 \end{array}\right] \left[\begin{array}{c} c_0 \\ c_1 \\ c_2 \\ c_3 \end{array}\right] = \left[\begin{array}{c} y_i \\ y^\prime_i \\ y_{i+1} \\ y^\prime_{i+1} \end{array}\right]. $$

Solving for the coefficients above, yields the cubic Hermite spline representation:

$$ y(t) = \left[\begin{array}{cccc} t^3 & t^2 & t & 1 \end{array}\right] \left[\begin{array}{cccc} \phantom{-}2 & -2 & \phantom{-}1 & \phantom{-}1 \\ -3 & \phantom{-}3 & -2 & -1 \\ \phantom{-}0 & \phantom{-}0 & \phantom{-}1 & \phantom{-}0 \\ \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \end{array}\right] \left[\begin{array}{c} y_{i} \\ y_{i+1} \\ y^\prime_{i} \\ y^\prime_{i+1} \end{array}\right]. $$

In a more general 2d context, we are seeking to represent curves as $\vec p(t) = (x(t), y(t))$. The endpoints are $\vec p_{i}$ and $\vec p_{i+1}$, and the tangents, $\vec v(t) = (x^\prime(t), y^\prime(t))$, at the endpoints are $\vec v_i$ and $\vec v_{i+1}$. Any point on the curve $\vec p(t)$ would then be:

$$ \vec p(t) = \left[\begin{array}{cccc} t^3 & t^2 & t & 1 \end{array}\right] \left[\begin{array}{cccc} \phantom{-}2 & -2 & \phantom{-}1 & \phantom{-}1 \\ -3 & \phantom{-}3 & -2 & -1 \\ \phantom{-}0 & \phantom{-}0 & \phantom{-}1 & \phantom{-}0 \\ \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \end{array}\right] \left[\begin{array}{c} \vec p_{i} \\ \vec p_{i+1} \\ \vec v_{i} \\ \vec v_{i+1} \end{array}\right] $$

Bézier basis.

Although the use of a Hermite basis means we can more easily line up the derivatives at the spline endpoints (where intervals meet), it isn't very natural to specify both endpoints and derivatives. Instead, we can use a Bézier basis, which is essentially a specification of points that lie on the spline endpoint tangents. That is, cubic Bézier curves are specified by four points, as shown below.


Click and drag (slowly) the control points
of the cubic Bezier curve above

The relationship between the cubic Hermite basis and the Bézier basis can be formulated as a matrix multiplication:

$$ \left[\begin{array}{c} \vec p_i \\ \vec p_{i+1} \\ \vec v_i \\ \vec v_{i+1} \end{array}\right] = \left[\begin{array}{cccc} \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \\ \phantom{-}0 & \phantom{-}0 & \phantom{-}0 & \phantom{-}1 \\ -3 & \phantom{-}3 & \phantom{-}0 & \phantom{-}0 \\ \phantom{-}0 & \phantom{-}0 & -3 & \phantom{-}3 \end{array}\right] \left[\begin{array}{c} \vec q_0 \\ \vec q_1 \\ \vec q_2 \\ \vec q_3 \end{array}\right], $$

where the $\vec q_0,\ \vec q_1,\ \vec q_2$ and $\vec q_3$ are called the control points of the Bézier curve. Notice that we are specifying $\vec p_i = \vec q_0$, $\vec p_{i+1} = \vec q_3$, and $\vec v_i = 3(\vec q_1 - \vec q_0)$ and $\vec v_{i+1} = 3(\vec q_3-\vec q_2)$. An important property of Bézier curves is that the entire curve lies in the convex hull of the control points - this is called the control polygon.

Finally, by multiplying the matrices in the Hermite and Bézier representation, we can obtain the representation of the curve in terms of the control points:

$$ \vec p(t) = \left[\begin{array}{cccc} t^3 & t^2 & t & 1 \end{array}\right] \left[\begin{array}{cccc} \phantom{-}2 & -2 & \phantom{-}1 & \phantom{-}1 \\ -3 & \phantom{-}3 & -2 & -1 \\ \phantom{-}0 & \phantom{-}0 & \phantom{-}1 & \phantom{-}0 \\ \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \end{array}\right] \left[\begin{array}{cccc} \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \\ \phantom{-}0 & \phantom{-}0 & \phantom{-}0 & \phantom{-}1 \\ -3 & \phantom{-}3 & \phantom{-}0 & \phantom{-}0 \\ \phantom{-}0 & \phantom{-}0 & -3 & \phantom{-}3 \end{array}\right] \left[\begin{array}{c} \vec q_0 \\ \vec q_1 \\ \vec q_2 \\ \vec q_3 \end{array}\right]\phantom{.} $$

$$ \phantom{\vec p(t)} = \left[\begin{array}{cccc} t^3 & t^2 & t & 1 \end{array}\right] \left[\begin{array}{cccc} -1 & \phantom{-}3 & -3 & \phantom{-}1 \\ \phantom{-}3 & -6 & \phantom{-}3 & \phantom{-}0 \\ -3 & \phantom{-}3 & \phantom{-}0 & \phantom{-}0 \\ \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \end{array}\right] \left[\begin{array}{c} \vec q_0 \\ \vec q_1 \\ \vec q_2 \\ \vec q_3 \end{array}\right]. \phantom{\left[\begin{array}{cccc} -1 & \phantom{-}3 & -3 & \phantom{-}1 \\ \phantom{-}3 & -6 & \phantom{-}3 & \phantom{-}0 \\ -3 & \phantom{-}3 & \phantom{-}0 & \phantom{-}0 \\ \phantom{-}1 & \phantom{-}0 & \phantom{-}0 & \phantom{-}0 \end{array}\right]} $$

Note that we can chain together Bézier splines to enforce continuity of the spline endpoints, as well as their derivatives.

de Casteljau's algorithm

Given a set of control points for a Bézier curve, we may want to render the curve. One option is to split up our interval into a uniformly spaced set of $t$ values, evaluate the basis and connect the dots. However, we can do better by employing another property of Bézier curves. Consider the control polygon formed by the points $\vec q_0,\ \vec q_1,\ \vec q_2$ and $\vec q_3$, which control a Bézier curve parametrized in the interval $[0,1]$. To evaluate the curve at a point $t = 0.5$:

  1. Take the midpoint of the 3 segments defining your control polygon: $\vec m_0,\ \vec m_1,\ \vec m_2$ (in red).
  2. Connect the three midpoints to form two new segments (dashed red lines).
  3. Compute the midpoints of these two segments: $\vec r_0$ and $\vec r_1$ (in gold).
  4. Connect the two midpoints to form a single new segment (dashed gold line, a bit hard to see).
  5. Compute the midpoint of this new segment: this is the Bézier curve evaluated at $t = 0.5$ (gray point).

This procedure is known as de Casteljau's algorithm. Effectively, this produces two new Bézier curves on either side of the point at $t = 0.5$. Need a more accurate rendering of your Bézier curve? Keep applying the above steps on the Bézier curves produced at every level of de Casteljau's algorithm. If you need to evaluate the Bézier curve at any $t$, divide the segments into fractions of $t$ and $1-t$ instead of a fraction of $1/2$ (i.e. at the midpoint). For example, $\vec m_0 = \vec q_0 + t (\vec q_1 - \vec q_0)$, and so on.

Rendering a Bézier curve recursively.

We can apply de Casteljau's algorithm recursively to render a Bézier curve with a specified "depth." That is, we start by evaluating the Bézier curve (with de Casteljau's algorithm) at $t = 0.5$ which produces two new Bézier curves. The first Bézier curve is defined by the control points: ${ \vec q_0,\vec m_0,\vec r_0,\vec p(0.5) }$. The second Bézier curve is defined by the control points: ${ \vec p(0.5),\vec r_1,\vec m_2,\vec q_3 }$. We then recurse on these two Bézier curves, decrementing our depth until a depth of zero is reached. When we reach a depth of zero (the base case of our recursion), then we just draw lines connecting the control points.

Animating characters.

The motion of a character does not only involve computing the position of its center of mass. We also need to consider how the arms, legs, head, etc. move in time. Just like a human, we can consider the skeleton of a character and model how the position and 3d rotations of the joints change over time, a process known as rigging. For example, consider a 2d model of an arm (lower right) and assume the lengths of the upper and lower arms, hand length are fixed in time. We can specify the position of the center of mass of the character, as well as the rotations at the shoulder, elbow and wrist. Our goal may be to animate the arm such that it touches a wall on the right.

    

Forward kinematics consists of directly specifying the angles between all the joints, perhaps using a spline curve as we just developed. Inverse kinematics formulates the motion as an optimization problem in which the angles at a particular pose are computed so that the hand is at a particular position. Care should be taken, however, using appropriate constraints to make sure the angles physically make sense.

Once we've animated the skeleton of our model, we can represent the character's skin using a mesh and associate each mesh point with a particular bone. Assuming we know the motion of the bone (e.g. with a spline or using inverse kinematics), the skin can be deformed using techniques called linear blend skinning in which the movement of each vertex is computed from a weighted contribution of each nearby bone's motion.


© Philip Claude Caplan, 2023 (Last updated: 2023-11-12)