Lesson 1 · Essential

Poses, keys and blending

A character's pose is a list of joint rotations; a clip is a list of poses over time; a blend mixes poses. How each works, the angle that spins the wrong way, and why blended feet slide unless the clips are kept in step.

Every lesson in this part runs on one small rig: a side-on character with 18 joints, its clips, and the systems an engine runs on them, stepped at a fixed 60 Hz. It’s 2D so you can see every joint, but each idea carries over to 3D unchanged, and where 3D adds a trap the text says so.

A skeleton is a tree of local transforms

A skeleton is a tree of joints. Each joint stores its transform relative to its parent: here, one rotation plus where it sits on the parent bone; in 3D, a rotation (a quaternion), a translation and sometimes a scale. That local transform is what clips store and what blending works on. It’s also why rotating a shoulder carries the elbow and hand with it.

Forward kinematics (FK) turns local transforms into world ones: walk the tree from the root, and each joint’s world transform is its parent’s world transform times its own. Store joints parent-first in one array and it’s a single loop, no recursion:

struct Joint { int parent; Transform local; };       // parent < own index, root's parent = -1

void forward_kinematics(const Joint* joints, int n, Transform* world) {
    world[0] = joints[0].local;
    for (int i = 1; i < n; ++i)
        world[i] = world[joints[i].parent] * joints[i].local;   // parent before child, always
}

That ordering is the whole trick: by the time you reach a joint, its parent is already done. Skinning then uses world[i] times each joint’s inverse bind pose to move the mesh.

A clip is a list of keyed poses

A clip is a list of poses at key times, plus what the engine keeps beside them: its length, whether it loops, how fast its root travels, and markers such as where each foot lands. To play it, the player finds the two keys either side of the current time and interpolates each joint between them.

The walk here is sampled at 30 keys a second, a common export rate. Fewer keys save memory and cost accuracy, and feet show it first, because a planted foot has to stay put while the body moves over it. With 4 keys a second the walk’s planted feet slide 106.8 cm for every metre walked, with 8 they slide 7.7, and with 30, 0.2. That’s the measure used throughout this section: how far any foot within 5 mm of the ground moves, per metre the character travels. Zero is perfect; a centimetre or two per metre is hard to see.

Shipping engines go further than a lower key rate: their compression stores each joint’s tracks in as few bits as an error budget allows, measuring the error where it shows, as the movement of points some distance out from each joint, roughly where the skin is (the open-source ACL library, Unreal’s default codec since 5.3, works this way). And because a clip stores local rotations, it can drive a skeleton with other proportions (retargeting): keep the rotations, take bone lengths from the new skeleton, and fix up feet and hands with IK.

Loading…

It starts on Lerp the numbers. Watch the near arm (its shoulder is ringed) and the plot under the character: twice per cycle the line strokes right across the plot, and on screen the arm flicks to point the wrong way for a frame. Tick ¼ speed to catch it.

The angle that spins the wrong way

The rig stores angles wrapped into −180° to 180°, the way atan2 returns them. The arms hang nearly straight down from a chest that points up, so their angle to the chest sits close to 180°: one key is just under, the next just over, stored as about −180°. In the walk, the left arm turns 3.7° from one such key to the next; lerping the stored numbers sends it 356.3° the other way, a full spin in a thirtieth of a second.

The fix is to interpolate along the shorter arc: take the difference, wrap it into ±180°, and go that far. In 2D that’s slerp, constant angular speed the short way round. Pick Short way (slerp) and the arm swings cleanly.

3D has the same trap in a different shape. A unit quaternion q and its negation -q are the same rotation, so two keys can be close as rotations but far apart as 4D vectors. Interpolating between them goes the long way. The fix is the same check, done on the dot product:

Quat nlerp(Quat a, Quat b, float t) {
    if (dot(a, b) < 0.0f) b = -b;                  // same rotation, the short way round
    return normalize(a * (1.0f - t) + b * t);       // lerp the components, renormalise
}

That’s nlerp: lerp the components and renormalise. It follows the same arc as slerp but not at constant speed. In 2D, a quarter of the way through a 90° turn it is 4.07° behind. For quaternions the error is smaller, 0.90°, because a quaternion’s angle is half the rotation’s. Over the small steps between keys or the gentle weights of a blend nobody can see it, and it’s much cheaper than slerp’s acos and sin, so engines blend with nlerp and keep slerp for when constant speed matters.

Smoother curves aren’t automatically better. Cubic (Catmull-Rom through the keys) overshoots where motion changes suddenly, as when a foot lands: the walk slides 1.1 cm per metre with cubic keys against 0.2 with linear. Hold (step) slides 122.8.

Blending two poses

Blending means mixing two poses joint by joint: each local rotation the short way by the weight, translations linearly. Because FK rebuilds the skeleton from the blended locals, every bone keeps its length.

Blending world positions instead breaks the skeleton. Halfway between a walk pose with the left foot forward and one with it back, averaging joint positions gives a shin 0.43 m long instead of 0.48. Blending the local angles keeps the lengths, but it isn’t free of trouble either: halfway between those two poses the leg points straight down, longer than the hips are high, and the ankle ends 6.3 cm under the floor. A blend of very different poses doesn’t look like either. Blends work when the poses being mixed line up.

Blend spaces

A blend space places clips at points in a parameter space and blends whichever surround the current value. In 1D, by speed: idle at 0, walk at 1.3 m/s, run at 3.5. Ask for 2.4 m/s and the walk and run get half each. In 2D (sideways and forward speed), Unreal by default splits the samples into triangles and blends the three at the corners of the triangle the point falls in, weighted by its barycentric coordinates; Unity’s 2D blend trees offer several layouts.

The parameter isn’t the speed you get. Halfway between the walk (1.3 m/s, 1.0 s cycle) and the run (3.5 m/s, 0.7 s), kept in step, the blend plays at the weighted cycle length and travels the weighted stride, so it moves at 2.21 m/s, not the 2.40 you asked for. Move the character at 2.40 regardless and the feet slide 10.3 cm per metre. Either let the animation move the character (root motion, lesson 4), scale the play rate to match, or place samples by their measured speed.

Loading…

The walk and the run at half weight each, at 2.4 m/s. The bars under the canvas are the two clips’ cycles: where each foot is down, the L and R markers where it lands, and the playhead. It starts on Own clocks, and the feet skate.

Sync: keeping clips in step

On their own clocks the clips drift in and out of step, because the run’s cycle is shorter: one clip plants a foot while the other lifts it, and the blended foot skates. At 2.4 m/s, on their own clocks, the feet slide 39.3 cm per metre.

The usual first fix is to play every clip at the same normalised time (0 to 1 through its cycle), at a rate that blends their lengths. That only works if every clip starts on the same foot. The run here starts on its right foot, as clips cut from motion capture often do, so at the same normalised time the feet are half a cycle apart: 76.4 cm per metre, worse than not syncing.

Sync markers fix it. Name the frames where each foot lands, and align clips by their position between markers rather than by raw time:

// Every clip in the group is at the same phase: the same fraction of the way from its L marker round to it again.
float phase = fmod(leader.time / leader.length - leader.marker_L + 1.0f, 1.0f);
for (Clip& c : group)
    c.time = fmod(c.marker_L + phase, 1.0f) * c.length;

Unreal’s sync groups do this: a leader (by default the clip with the most weight) sets the pace and followers match it by markers, using only the markers every clip in the group has. With markers the slide drops to 4.7 cm per metre. What remains is the difference between the clips themselves: the walk keeps each foot down for 60% of its cycle and the run for 34%, so between those, one clip has the foot down and the other is lifting it. Kovar and Gleicher’s registration curves generalise the idea, working out the timing, alignment and foot constraints between motions automatically.

Takeaways

  • A pose is local transforms; FK walks them parent-first to world transforms in one loop.
  • A clip is keys sampled over time. Fewer keys means sliding feet; smoother curves can overshoot.
  • Interpolate rotations the short way: wrap the angle difference in 2D, flip -q when the dot product is negative in 3D. Nlerp is the cheap, near-enough choice for blending.
  • Blend local rotations, never world positions. Blends work when the poses line up.
  • A blend space’s parameter isn’t the speed you get; and blended cycles need sync, by markers when clips start on different feet.

In the lab

The animation lab runs this rig with everything exposed: every interpolation mode, any key rate, a 2D direction blend space seen from above, and the sync options on any pair of clips.

Open the walk and run blend space in the animation lab →

Further reading