glTF Transform Matrices in Nested Node Trees
In a glTF scene, 3D objects are organized hierarchically as directed acyclic graphs of nodes. Evaluating this hierarchy requires computing the absolute world transform for each node by combining parent and child transformations from the root down to the leaf nodes. This article explains how glTF evaluates local transformations—whether defined by explicit matrices or individual translation, rotation, and scale components—and recursively combines them through matrix multiplication to position objects correctly in world space.
Local Node Transforms
Every node in a glTF asset defines its local transformation relative to its parent. This is defined in one of two mutually exclusive ways:
TRS Properties: The node supplies separate
translation,rotation, andscalevectors.translation: A 3D vector \([T_x, T_y, T_z]\) defining displacement.rotation: A normalized unit quaternion \([q_x, q_y, q_z, q_w]\) in \((x, y, z, w)\) order.scale: A 3D vector \([S_x, S_y, S_z]\) defining axis-aligned scaling factors.
When TRS properties are provided, the local transformation matrix \(M_{\text{local}}\) is constructed by multiplying translation (\(T\)), rotation (\(R\)), and scale (\(S\)) in that exact order: \[M_{\text{local}} = T \times R \times S\]
Matrix Property: The node specifies a single 16-element array representing a \(4 \times 4\) affine transformation matrix stored in column-major order. If a matrix is supplied, TRS properties are ignored.
If neither a matrix nor TRS properties are declared, \(M_{\text{local}}\) defaults to the \(4 \times 4\) identity matrix.
The Traversal and Accumulation Process
glTF uses column vectors, meaning points are transformed via \(v' = M \times v\). As a result, transformations are evaluated from right to left, and child coordinates are mapped into parent space.
To evaluate the scene, the runtime traverses the node tree from the root nodes downward, typically using depth-first search (DFS) or breadth-first search (BFS). The evaluation proceeds according to the following rules:
Root Nodes: For nodes that have no parent, their world matrix \(M_{\text{world}}\) is simply their local matrix: \[M_{\text{world, root}} = M_{\text{local, root}}\]
Child Nodes: For any nested child node, its world matrix is calculated by multiplying its parent's accumulated world matrix by its own local matrix: \[M_{\text{world, child}} = M_{\text{world, parent}} \times M_{\text{local, child}}\]
Mathematical Order and Coordinate Systems
Because matrix multiplication is non-commutative, preserving the order \(M_{\text{parent}} \times M_{\text{child}}\) is critical. This ensures that the local operations of the child (scaling, rotating around its own origin, and translating) happen first before being rotated, scaled, and translated by the parent's frame of reference.
When applied to vertex coordinates \(v_{\text{local}}\) stored in a mesh primitive attached to a child node, the final position in world space is: \[v_{\text{world}} = M_{\text{world, parent}} \times M_{\text{local, child}} \times v_{\text{local}}\]
glTF defines its coordinate system as right-handed, where \(+Y\) points up, \(+Z\) points forward (towards the viewer), and \(+X\) points to the right. The combined world transform preserves this convention unless negative scaling introduces a reflection, in which case vertex winding orders may need adjustment during rendering. Cached world matrices can be invalidated and recomputed dynamically whenever a node's local TRS or matrix changes during animation.