Computer Graphics · Animation

Additive Animation Layers and the Weights That Normalize Them

How a delta pose composes onto a base without double-counting, why blend weights need a normalized space, and how the same arithmetic mixes clips, layers, and states.

Two clips describing the same character rarely add cleanly. A walk cycle and an arm wave both drive the shoulder, and playing them both at once by adding their joint values produces a pose that is neither. The machinery that makes independent motions compose is the additive layer: a clip stored as deltas from a base, blended with a weight, and applied on top of a running pose. The arithmetic is a handful of operations per joint. The design decisions are all in where the base is chosen, how the weight is defined, and what happens when several weights must sum to one.

A pose is a value per joint, and blending is a per-joint mix

The running pose of a character is one rotation and one translation per joint in the hierarchy. Blending two poses means mixing them per joint, and the two mix operations are different. Translation mixes linearly: the blend of two offsets with weights w and 1-w is their weighted sum. Rotation mixes on the sphere, and the correct operation for a weighted mix is slerp or its normalized-linear approximation, with the shortest-arc sign fix applied per joint.

The per-joint nature of the mix is what lets one joint be animated by one clip and its neighbour by another, and it is also the source of the first constraint on layer arithmetic: a weight that applies to a whole layer must be applied per joint, and it must not disturb joints the layer does not touch. A layer that only drives the arms still produces a mix at every joint if it is blended naively, because a zero-rotation from the layer blended against the base’s rotation is not a no-op; it is a pull of the base rotation toward identity. The definition of “does not touch” has to be encoded as a skip, not as a zero value.

The distinction between a base pose and a delta is the heart of additive animation. A base pose is absolute: it says the shoulder sits at some orientation. A delta is relative: it says the shoulder moves by some amount from wherever it is. Storing a layer’s motion as deltas means it can be applied on top of any base, which is what makes one additive clip reusable across many absolute clips. The delta is computed by recording a reference pose and subtracting it, and the subtraction is per joint: translation deltas subtract, rotation deltas are the relative rotation from reference to current.

How a delta composes onto a base

For translations the composition is the sum. For rotations the composition is a multiplication in quaternion space, applied on the correct side, and the correct side is a convention that must match the one the hierarchy’s local transforms use. If a delta $d$ was recorded relative to reference $r$ as the rotation that takes $r$ to the observed pose $o$, then $o = d r$ with the delta on the left, and applying it to a new base $b$ gives $d b$. Getting the side wrong applies the delta in the base’s frame instead of the joint’s, and the limb rotates about the wrong axis.

The weight is a scalar that scales how much of the delta to apply. A translation delta scales linearly; a rotation delta scales in the angle, so a weight of 0.5 applies half the angular difference. The natural representation for a weighted rotation delta is to take the relative rotation, convert it to axis-angle, scale the angle by the weight, and convert back. That is exactly what a partial slerp from identity toward the delta does, and it is why the weighted application of a rotation delta costs a trig function per joint where a naive linear mix would not.

/* Apply an additive rotation delta to a base quaternion, with a weight.
   delta and base are unit quaternions; result is unit. w in [0,1]. */
static void apply_rotation_delta(const float delta[4], const float base[4],
                                 float w, float out[4]) {
    /* Scale the delta's angle by w via axis-angle, then compose on the left. */
    float angle = 2.0f * acosf(delta[0]);       /* scalar-first quaternion */
    if (angle < 1e-6f) {                        /* near identity: linear mix */
        for (int i = 0; i < 4; i++)
            out[i] = base[i] + w * (delta[i] - (i == 0 ? 1.0f : 0.0f));
        float n = sqrtf(out[0]*out[0] + out[1]*out[1] + out[2]*out[2] + out[3]*out[3]);
        for (int i = 0; i < 4; i++) out[i] /= n;
        return;
    }
    float s = sinf(w * angle) / sinf(angle);
    float scaled[4] = { cosf(w * angle), delta[1] * s, delta[2] * s, delta[3] * s };
    /* compose: scaled * base */
    out[0] = scaled[0]*base[0] - scaled[1]*base[1] - scaled[2]*base[2] - scaled[3]*base[3];
    out[1] = scaled[0]*base[1] + scaled[1]*base[0] + scaled[2]*base[3] - scaled[3]*base[2];
    out[2] = scaled[0]*base[2] - scaled[1]*base[3] + scaled[2]*base[0] + scaled[3]*base[1];
    out[3] = scaled[0]*base[3] + scaled[1]*base[2] - scaled[2]*base[1] + scaled[3]*base[0];
}

The weight lives in [0,1] but nothing in the arithmetic enforces the range, and a weight outside it is the common bug: a weight of 1.5 applies one and a half times the delta, rotating a joint past the intended pose and, when the delta was recorded from an extreme, past the rotation’s representable range. Clamping the weight is the safe policy, and it is a policy, not a property of the composition.

Weight normalization is about the blend space, not the blend

When several layers or clips are active at once, their weights must live in a shared space or the result depends on the order of combination. The standard arrangement is normalized blending: each source has a raw weight, all raw weights sum to one, and the mix is the weighted average. The normalization is not a detail; it is what makes the blend commutative and what makes a slider between two clips well defined.

The failure it prevents is double-weighting. If three clips drive a joint with weights that sum to two, a joint that should sit near the average of the three instead sits two-thirds past it, and the pose over-extends. Normalizing divides each weight by the sum, which keeps the result inside the convex hull of the inputs. The cost is that a single clip with weight one is unaffected, while adding a second clip with weight one halves the first clip’s influence, which is the intended semantics of “fade between” rather than “add on top”.

Additive layers sit outside the normalized blend. A normalized mix produces a base pose, and additive deltas are then applied on top of it, each with its own weight that does not participate in the normalization. That two-stage structure, blend then add, is what lets a breathing layer animate the chest while a walk-to-run blend occupies the legs: the legs come from the normalized mix, the chest comes from the base plus an additive delta. A system that folds additive weights into the normalization loses this, because the additive layer’s weight then competes with the base clips for the same unit of weight.

Subtractive layers and the base-pose trap

A delta can be negative as well as positive, and the subtractive case exposes the base-pose dependency. An additive clip recorded against one reference pose, subtracted from another pose, is only meaningful if the two references align; a clip that means “arm raised” relative to an arms-down reference, applied as a subtract to a pose already holding arms down, produces a rotation through the arm’s full range and out the other side. The reference pose is part of the clip’s meaning, and it must be stored and matched.

The sign conventions of quaternion keys make this worse, because a reference pose stored with an opposite sign to the base pose composes as a large rotation instead of a small correction. Matching signs between the delta’s reference frame and the base it is applied to is a precondition, and the shortest-arc test that fixes interpolation signs is the same test that must be applied when the delta was authored in a different pipeline.

Recording deltas at runtime is another way the base trap appears. A system that captures the current pose as a reference, plays a motion, and computes deltas from the difference, then later applies those deltas to a different pose, is assuming the capture pose and the application pose have the same joint frame. A character that has since been re-rigged, or that has a different root transform, breaks the assumption, and the applied delta is a rotation in the wrong frame. Storing the reference with the deltas and checking it, or re-recording, is the remedy.

Layers and states are the same arithmetic at different granularity

A state machine that fades between idle, walk, and run is doing normalized blending between three sources with weights derived from the machine’s transition rules. The fade duration, a time over which the weights ramp from one source to the other, is the same shape as a layer weight animated over time. The unifying idea is that all mixing is per-joint weighted combination, and the differences between “clip”, “layer”, and “state” are differences in how the weights are driven, not in the combination.

That unification has a concrete cost advantage. Because the mix is per joint and the weights are scalars, a tree of blend nodes can be flattened into a single weighted sum per joint per source, and the per-joint trig work for rotation blending dominates the scalar bookkeeping. A deep hierarchy of layers each paying a slerp per joint is the expensive form, and the flattening is the optimization: compute normalized weights once, then one weighted combination per joint over all sources, rather than a combination per layer.

The output of the whole blend is one pose, and that pose is what the skinning pipeline consumes. If the blend is correct and the pose is sane, skinning cannot tell how many layers produced it; if the blend produced an over-extended pose by failing to normalize, the skinning stage faithfully renders the error, and the visible symptom is a limb that is too long, a common and misattributed artifact in character work.

Where the arithmetic must stay exact

Floating point error accumulates through the layers, and the places that must stay exact are the ones that encode hard invariants. The unit constraint on quaternions is one: every composition and every slerp renormalizes, and a blend that skips the renormalization of the running pose between layers lets the error grow from rounding to visible drift over a long sequence of additive applications. A pose built from a hundred small deltas is the worst case, because each delta application is a composition followed by normalization and each rounds.

The zero case is the other. A layer with weight zero should be a no-op, and it is, provided the implementation short-circuits the composition rather than computing a delta scaled by zero and composing it. Scaling a rotation delta by zero requires evaluating the axis-angle of a possibly singular delta, which is where a NaN can enter; the short-circuit avoids touching the delta at all and is the only way the zero weight is truly free. The same principle applies to a blend where one source has weight one and all others zero: the exact source is copied, not recomputed through the mix.

The interaction with key reduction is that a delta channel’s tolerance is a tolerance on a value that will be scaled and added, and an error in a delta is amplified when the weight is high and cancelled when it is low. Two reduced delta clips, each within tolerance, can produce an applied pose whose error exceeds the sum of the tolerances, because the errors compose in the joint frame before the pose is consumed. Reducing the base and the delta to a shared, tighter tolerance is the only guarantee that the composed pose stays inside a bound.

References

  1. glTF 2.0 Specification, Animation Samplers — Khronos Group
  2. Human Motion Synthesis with a Database of Base Movements — Kovar and Gleicher, Eurographics 2004
  3. Motion Graphs — Kovar, Gleicher and Pighin, SIGGRAPH 2002