How glTF Stores Relative Deltas for Morph Targets
glTF (GL Transmission Format) handles morph targets, commonly known as shape keys, by storing additive displacement vectors rather than full duplicate geometry. Instead of redefining entire meshes for each target pose, glTF defines relative positional deltas (\(\Delta\)) that measure the distance and direction vertices move from their base rest pose. This article explains how glTF structures these relative positional deltas, how accessors and sparse data structures optimize storage, and how real-time rendering engines compute the final deformed vertex positions.
The Mechanism of Additive Deltas
In 3D deformation, storing full vertex arrays for every target pose drastically inflates asset size and memory consumption. glTF 2.0 solves this by using additive morph targets. Each shape key stores only the difference between the base state and the deformed state:
\[\Delta P = P_{\text{target}} - P_{\text{base}}\]
During rendering, the graphics engine calculates the final position (\(P_{\text{final}}\)) of each vertex by multiplying each target's delta by its corresponding weight (\(w\)) and adding the sum to the original base position:
\[P_{\text{final}} = P_{\text{base}} + \sum_{i=0}^{n-1} (w_i \cdot \Delta P_i)\]
This approach allows multiple shape keys—such as blinking, smiling, and brow-raising—to blend simultaneously without conflicting vertex specifications.
The glTF Schema Structure
In a glTF file, morph targets are organized inside the
mesh.primitives object under the targets
array.
Each entry in the targets array is a dictionary mapping
vertex attribute semantics to specific accessor objects:
{
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"NORMAL": 1
},
"targets": [
{
"POSITION": 2,
"NORMAL": 3
},
{
"POSITION": 4
}
]
}
],
"weights": [0.0, 0.0]
}
]
}In this structure:
- The base primitive defines its rest geometry via
attributes.POSITION(accessor0). - Each dictionary in
targetsrepresents a distinct shape key. - The
POSITIONproperty inside a target (e.g., accessor2) does not store absolute coordinates. Instead, it points to raw buffer data containing 3-component floating-point vectors (VEC3) that represent the \((dx, dy, dz)\) offsets relative to accessor0. - The vertex count in each target accessor must exactly match the vertex count of the base attribute accessor, maintaining a direct 1:1 index alignment.
Data Types and Quantization
Positional deltas are typically stored as single-precision 32-bit
floats (5126 / FLOAT). However, glTF 2.0 also
allows signed integers to be used for deltas:
5120(BYTE)5122(SHORT)
When signed integer types are used, the accessor must enable
normalization ("normalized": true). This maps the minimum
and maximum displacement range directly to \([-1.0, 1.0]\), offering a quantized data
format that significantly reduces binary buffer size without requiring
external extensions.
Optimizing Localized Deformations with Sparse Accessors
Most shape keys modify only a localized cluster of vertices. For instance, an eyelid blink leaves thousands of body and clothing vertices completely stationary with a delta of \((0, 0, 0)\).
To prevent storing extensive arrays of zeroed vectors, glTF supports
sparse accessors (accessor.sparse). A
sparse accessor constructs the target array virtually by combining:
- A default initialization filled with zeros.
- An
indicesbuffer identifying only the specific vertex indices that move. - A
valuesbuffer containing the corresponding \((dx, dy, dz)\) displacement deltas for those active indices.
When a client engine unpacks or streams the glTF primitive, it expands the sparse data into full GPU buffers or applies the sparse indices directly during compute skinning passes, keeping network transfer footprints and disk storage minimal.