How glTF Represents Blend Shapes for Deformation
In glTF (GL Transmission Format), non-rigid mesh deformations such as facial expressions and organic flexing are achieved using morph targets, commonly known as vertex blend shapes. Rather than storing full duplicates of the geometry for every variation, glTF handles blend shapes by storing relative displacement vectors alongside the base mesh attributes, which runtime engines combine using a series of scalar weights. This article explains the data structure, math, and animation mechanisms glTF uses to efficiently encode and render these non-rigid deformations.
The Base Primitive and Morph Targets
In the glTF JSON schema, 3D geometry is organized under
meshes, where each mesh contains one or more
primitives. A standard primitive holds core vertex
attributes—such as POSITION, NORMAL, and
TANGENT—defined by accessors that point to the underlying
binary buffer data.
To support blend shapes, a primitive includes an optional
targets array. Each entry in the targets array
represents a distinct morph target and contains an object mapping
attribute names to accessor IDs. The attributes supported in morph
targets are:
POSITION: Stores 3D displacement vectors (offsets).NORMAL: Stores normal displacement vectors.TANGENT: Stores XYZ tangent displacement vectors.
Additive Delta Storage
A critical aspect of glTF's representation is that morph target data is strictly stored as deltas (displacements) rather than absolute coordinates. For example, if a vertex at position \((0, 5, 0)\) moves to \((0, 6, 0)\) in a given expression, the morph target stores \((0, 1, 0)\).
Storing relative differences provides significant advantages:
- It simplifies blending, allowing multiple morph targets to be applied simultaneously.
- It improves compression efficiency because many target vertices remain stationary, yielding displacement values of zero.
To optimize bandwidth further, glTF implementations frequently use
sparse accessors (accessor.sparse). Sparse accessors only
record vertices that deviate from the neutral state, omitting untouched
vertices entirely.
The Blending Formulation
The deformation state at runtime is governed by an array of scalar
float values called weights. These weights can be defined
globally on the mesh as default values or overridden at the
node level via node.weights.
The final transformed attribute is computed using additive blending:
\[\text{Attribute}_{\text{final}} = \text{Attribute}_{\text{base}} + \sum_{i=0}^{n-1} (w_i \times \Delta\text{Attribute}_i)\]
Where:
- \(\text{Attribute}_{\text{base}}\) is the neutral vertex position, normal, or tangent.
- \(w_i\) is the scalar weight for target \(i\) (typically clamped between \(0.0\) and \(1.0\), though negative values and overshooting values above \(1.0\) are mathematically valid).
- \(\Delta\text{Attribute}_i\) is the displacement vector for target \(i\).
After computing final normals and tangents, shaders typically re-normalize the vectors to correct for non-linear interpolation distortions.
Animating Blend Shapes
glTF integrates blend shape control directly into its core animation system. To animate a morph target over time:
- An animation channel designates a
targetpointing to a specificnode. - The channel's
pathis set to"weights". - An animation sampler provides the timeline inputs and the corresponding scalar array outputs (one weight per morph target at each keyframe).
- Engines interpolate these weights (via
LINEAR,STEP, orCUBICSPLINEinterpolation) and apply the resulting values to the target node during the frame update.