Each vertex contains:
position: [f32; 3]
normal: [f32; 3]
uv: [f32; 2]
joints: [u32; 4]
weights: [f32; 4]
joints indexes the current skin’s joint list. weights determines how much each joint matrix affects the vertex. Imported weights are normalized before upload.
Every glTF node retains its decomposed local transform:
translation
rotation quaternion
scale
World matrices are rebuilt recursively from the parent-child graph. Animation channels replace or blend the local translation, rotation, or scale before hierarchy evaluation.
For a primitive attached to a skinned mesh node, each GPU joint matrix is computed as:
inverse(mesh node world)
× joint node world
× inverse bind matrix
The primitive model matrix then applies:
scene normalization × mesh node world
This keeps the glTF skin calculation in mesh-local space while still allowing the whole character to be centered and scaled for the example viewer.
The vertex shader reads a runtime array of joint matrices from a read-only storage buffer. It blends four matrices using the vertex weights and transforms both position and normal.
Each primitive owns its own joint buffer. This is deliberate: the same glTF skin may be attached to multiple mesh nodes, and the mesh-node inverse in the skin formula can differ for each attachment.
The animation system samples translation, rotation, and scale channels. Linear channels use vector interpolation or quaternion spherical interpolation. Step channels hold the previous sample.
Manual joint rotation is applied after the animation sample:
base pose
↓
animation sample + blend
↓
manual X/Y/Z rotation offset
↓
world hierarchy
↓
GPU joint matrices
This allows a performer to bend or twist an animated character without stopping the clip.
This example intentionally does not implement:
Those are separate layers and should not be hidden inside the first skeletal foundation.