3D Coordinate Systems and Transforms Without the Guesswork
Understand local and world space, axes, handedness, matrices, quaternions, scale, units, and the transform failures that break exported 3D assets.
Most “the model is rotated, mirrored, tiny, or somewhere off-screen” bugs are coordinate problems. Geometry can be perfectly valid while the transforms that place it are misunderstood. Because tools display friendly position, rotation, and scale fields, it is easy to forget that every value is meaningful only inside a coordinate system and relative to a parent.
A reliable pipeline makes four things explicit: axis directions, handedness, measurement units, and transform hierarchy. Once those conventions are written down, conversion becomes arithmetic rather than a sequence of 90-degree guesses.
A coordinate is always relative to a space#
The point (1, 2, 3) has no complete physical meaning by itself. It could be one meter right, two meters up, and three meters forward from an object's origin; or the same offsets from the scene origin; or a camera-space point after view transformation.
Common spaces include:
- object or local space, where mesh vertex positions are stored;
- parent space, where a node's local transform is evaluated;
- world space, after all ancestor transforms are composed;
- view space, relative to the camera;
- clip space, after projection and before perspective division;
- normalized device coordinates, after division by the homogeneous component;
- screen space, in viewport pixels or a related display convention.
If a vertex is at local X = 2 and its node is translated world X = 10 with no parent or rotation, the world X is 12. If the parent is scaled by 3 on X, composition changes the result. Debug reports should therefore say “local position” or “world position,” never just “position.”
glTF's axis and unit convention#
The glTF 2.0 specification defines a right-handed coordinate system. Positive Y is up, positive Z is forward, and the front of a glTF asset faces positive Z. Linear distances are in meters. Angles are in radians. Positive rotation is counterclockwise.
That convention is the interchange contract, not necessarily the authoring tool's interface convention. An exporter converts from its source space into glTF. An importer may convert again into an engine's internal space. Conversion should occur at a deliberate boundary exactly once.
A 180-centimeter character authored in a centimeter-based tool should arrive approximately 1.8 glTF units tall. If it arrives 180 units tall, the scale conversion was missed. If a later importer also multiplies by 0.01, a correctly converted 1.8-meter character becomes 0.018 meters tall.
Right-handed and left-handed systems#
Handedness describes the orientation relationship among three axes. It is not equivalent to which axis points up. Two systems can both use Y-up while using opposite handedness or different definitions of forward.
Changing handedness requires a reflection, often implemented by negating one axis. Reflection has consequences:
- triangle winding reverses unless corrected;
- normals and tangents need consistent transformation;
- tangent handedness can change;
- quaternion components cannot be converted by an arbitrary sign guess;
- animation curves must follow the same basis conversion;
- camera and light directions must be converted too.
If geometry looks correct only with back-face culling disabled, inspect the transform determinant and winding. A negative determinant indicates an orientation-reversing transform. Disabling culling hides the symptom while leaving normals, tangents, collision, and later operations at risk.
Describe conversion as a basis matrix#
For a durable conversion, write the destination basis vectors in source coordinates and form one basis-change matrix. Apply that same definition to positions, directions, node transforms, inverse bind matrices, and animation. Positions include translation and use a homogeneous component of 1; directions omit translation and use 0. Normals require the inverse-transpose rule when scale is involved.
Verify the matrix with three unit vectors before processing a mesh. Source +X, +Y, and +Z should land on the documented destination directions. Then test a non-axis vector such as normalized (1, 2, 3) so a coincidental swap is less likely to pass. For transforms, a typical change of basis has the form C × M × C inverse, where C performs the coordinate conversion. The exact multiplication order follows the vector convention of the math library, so capture expected numeric outputs in unit tests.
Keep conversion metadata in logs: source convention, destination convention, unit factor, matrix, determinant, and tool version. When a later asset arrives mirrored, this record distinguishes a changed source convention from a changed converter.
If transforms are serialized as 32-bit floats, a matrix contains 16 components and 64 bytes, a quaternion contains 4 components and 16 bytes, and a translation contains 3 components and 12 bytes. These tiny values rarely drive asset size, but stating them in a binary inspection report catches truncated arrays and wrong element types immediately.
Local transforms and hierarchy#
A node transform positions a node relative to its parent. Child transforms inherit their ancestors. A wheel located one meter from a car origin follows the car because its world transform is the car's world matrix multiplied by the wheel's local matrix.
glTF node graphs form strict trees inside a scene: a node has at most one parent. A node may reference a mesh, camera, skin, children, or a combination allowed by the schema. The hierarchy is semantically important for animation and component motion, not just an outliner organization.
Use intermediate empty nodes when they express a stable pivot or coordinate conversion. Avoid long chains of unexplained nodes named Empty.001, correction, correction2, and final. Each ancestor becomes another place for scale, reflection, or pivot mistakes.
To debug a child, print its ancestor chain with every local transform and the computed world matrix. Testing the mesh at the scene root can determine whether corruption lies in vertex data or hierarchy.
Translation, rotation, and scale order#
glTF can store a node as a 4 × 4 matrix or as separate translation, rotation, and scale properties. For TRS, the local transform is composed as translation × rotation × scale. With column-vector notation, scale acts on a point first, then rotation, then translation.
Order matters. Rotating a translated point around the origin differs from translating an already rotated point. Consider local point (1, 0, 0):
- scale by 2 gives (2, 0, 0);
- rotate 90 degrees around positive Z gives approximately (0, 2, 0);
- translate by (10, 0, 0) gives approximately (10, 2, 0).
Translating first would move the point to (11, 0, 0), then rotate it to approximately (0, 11, 0). Same inputs, different result.
Do not infer multiplication order from how a library names its method. Libraries differ in vector orientation and whether a call pre-multiplies or post-multiplies. Verify one known point with a small test.
Euler angles and quaternions#
Euler angles describe rotation as a sequence around axes. They are understandable in an interface but depend on rotation order. X = 30°, Y = 45°, Z = 10° can produce different orientations under XYZ and ZYX order. Certain alignments also lose an independent degree of freedom, commonly called gimbal lock.
glTF stores node rotation as a unit quaternion in X, Y, Z, W order. The identity is (0, 0, 0, 1). Quaternions q and −q describe the same orientation, which matters when interpolating animation samples: adjacent values should use compatible signs to take the intended path.
Normalize quaternions before use. A non-unit quaternion can introduce incorrect results in code that assumes unit length. For a 90-degree rotation around Y, half the angle is 45 degrees, giving approximately (0, 0.7071, 0, 0.7071). Degrees must be converted to radians before standard sine and cosine functions.
When converting animation from another basis, transform the represented rotation as a change of basis rather than swapping quaternion fields until one pose looks right. Test identity, single-axis 90-degree poses, and a combined pose.
Scale is more dangerous than it looks#
Uniform scale multiplies all axes equally. Non-uniform scale, such as (2, 1, 0.5), changes angles between transformed surface directions and requires normals to be transformed with the inverse transpose of the relevant matrix rather than like positions.
Negative scale reflects an axis. It can reverse winding and create a hierarchy whose visual result appears fine in the authoring viewport but exports with inconsistent faces. Applying transforms may bake reflection into vertices, but it can also change child behavior, animation, or modifier results. Make a controlled duplicate before destructive application.
Zero scale makes a transform singular. It cannot be inverted, which breaks operations that require converting between spaces. Use visibility or an explicit state instead of scaling an object to exactly zero when downstream inverse transforms are needed.
glTF animation targets translation, rotation, and scale properties. A node stored only with a matrix must be decomposed to animate those channels, and not every matrix decomposes cleanly to TRS. Shear is a common obstacle. Keep animated hierarchies decomposable and validate exported channels.
Origins, pivots, and bounds#
The mesh origin is the coordinate basis of its vertices; a node transform places that origin. Moving vertices in edit mode and moving the object in object mode can look identical in one frame while producing different pivots.
A door should rotate around its hinge, a wheel around its axle, and a product turntable around a deliberate presentation origin. Incorrect origins cause animation arcs, camera framing, physics, and user manipulation to fail even when static placement looks fine.
Bounds must also be evaluated in the right space. An accessor's minimum and maximum POSITION values describe local mesh data. A world-space box must transform its corners or use a correct oriented-bound method. Simply transforming the local minimum and maximum points fails under rotation.
An outlier vertex at coordinate 1,000,000 can make automatic framing place a normal 2-meter object into a nearly invisible portion of the camera view. Inspect local bounds, node translations, and aggregate world bounds separately.
Cameras and projection#
A perspective camera maps a view frustum to clip space. Vertical field of view, aspect ratio, near plane, and far plane affect what is visible and how depth precision is distributed. A model behind the camera or closer than the near plane can be valid but invisible.
Depth precision is not distributed uniformly under a conventional perspective projection. Setting a near plane to 0.0001 and far plane to 1,000,000 asks one depth buffer to cover a ratio of 10 billion to 1. Raise the near plane and lower the far plane to what the scene needs. Z-fighting between close surfaces is often a projection-precision problem, not a texture bug.
Orthographic projection does not make distant objects smaller. Automatic framing logic must use orthographic magnification rather than moving the camera backward and expecting perspective shrinkage.
A conversion test fixture#
Before converting production assets, build a tiny diagnostic scene containing:
- colored arrows for positive X, Y, and Z;
- labels readable from the intended front;
- one clockwise-wound and one counterclockwise-wound triangle;
- a cube with a distinct image on every face;
- a parent rotated 90 degrees with an offset child;
- an object with non-uniform scale;
- a simple two-keyframe rotation animation;
- a camera aimed at a known marker;
- a one-meter reference bar;
- a mesh with an off-center pivot.
Export it through the same path as production. If axes, faces, dimensions, pivots, and animation survive, the fixture becomes a regression asset. It is faster and more diagnostic than using a complex character whose intended orientation is unclear.
Transform debugging checklist#
- Source and destination axis conventions are documented.
- Unit conversion occurs once and produces expected dimensions.
- A basis conversion covers geometry, normals, tangents, animation, cameras, and lights.
- Negative determinants and reversed winding are reported.
- Local and world values are labeled in diagnostics.
- Quaternion values use X, Y, Z, W order and unit length.
- Degree-to-radian conversion happens at the correct boundary.
- Animated nodes have decomposable TRS transforms.
- Non-uniform scale receives correct normal transformation.
- Pivots match interaction and animation requirements.
- Local and aggregate world bounds contain no unexplained outliers.
- Near and far planes suit the actual scene scale.
Sources and further reading#
- Khronos glTF 2.0 specification: coordinate system and units
- Khronos glTF Tutorial: scenes and nodes
- Khronos glTF 2.0 specification: transformations
- Blender Manual: object origin
- Three.js documentation: Matrix4
Coordinate problems become manageable when a pipeline treats conventions as data. State the source and destination bases, convert once, test known fixtures, and inspect composed world transforms. That method is faster than repairing every model by eye.