How glTF Assigns Joint Indices Across Vertex Streams
This article explains how the glTF (GL Transmission Format) specification handles skeletal skinning when vertices are influenced by more than four bones. It covers the mechanics of partitioning joint data across multiple vertex attribute streams, how joint indices map directly to a model's skin hierarchy, and the requirements for corresponding weights to ensure proper hardware-accelerated deformation.
The 4-Component Attribute Stream Limit
In glTF, vertex attributes for skinning are defined using 4-component
vectors (VEC4). A single stream set consists of:
JOINTS_0: Stores the indices of the bones influencing the vertex.WEIGHTS_0: Stores the linear blend skinning weights corresponding to those bones.
Because standard graphics hardware traditionally processes vertex
attributes in 4-component vectors, four bone influences per vertex are
the default baseline. When a mesh requires more than four bone
influences per vertex (e.g., 5 to 8 bones), glTF expands this system by
adding secondary attribute streams: JOINTS_1 and
WEIGHTS_1. If 9 to 12 influences are needed,
JOINTS_2 and WEIGHTS_2 are introduced,
continuing incrementally (JOINTS_n,
WEIGHTS_n).
Joint Index Resolution
Each joint index inside every JOINTS_n stream directly
references an index in the joints array of the associated
skin object, not the global node array of the glTF
document.
- The Skin Palette: A glTF
skindefines ajointsarray containing references to node IDs (e.g.,"joints": [ 10, 11, 12, 13, 14 ]). - Direct Indexing: An index value of
3in eitherJOINTS_0orJOINTS_1refers to the fourth entry in that specificskin.jointsarray (node ID13in this example). - No Stream-Based Offsets: Joint indices in secondary
streams (
JOINTS_1,JOINTS_2) do not automatically offset based on previous streams. A joint index of2inJOINTS_1points to index2of theskin.jointsarray, exactly as it would inJOINTS_0.
Pairing and Data Types
For skinning to function correctly, joint and weight attributes must follow strict pairing and formatting constraints:
- Index-Weight Correspondence: The k-th
component of
JOINTS_nis strictly tied to the k-th component ofWEIGHTS_n. For example,JOINTS_1.yis weighted byWEIGHTS_1.y. - Joint Data Types: Attributes under
JOINTS_nmust use eitherUNSIGNED_BYTE(0–255) orUNSIGNED_SHORT(0–65535). Floating-point formats are not permitted for joint indices. - Weight Data Types:
WEIGHTS_ncan be stored asFLOAT, normalizedUNSIGNED_BYTE, or normalizedUNSIGNED_SHORT.
Normalization and Unused Influences
When using multiple vertex streams, weights must be normalized across all active streams simultaneously:
- Cumulative Sum: The sum of all weights across all
streams for a single vertex (\(\sum
\text{WEIGHTS\_0} + \sum \text{WEIGHTS\_1} + \dots\)) must equal
1.0. - Padding Unused Slots: If a vertex only uses 5
bones, it requires both
JOINTS_0/WEIGHTS_0(4 bones) andJOINTS_1/WEIGHTS_1(1 bone). The remaining three unused components inJOINTS_1andWEIGHTS_1must be padded. The unused weight components must be set to0.0. The unused joint indices are typically set to0or any valid index within theskin.jointsrange to avoid out-of-bounds errors during shader execution.