Here is the bug you have all met. A pose leaves the sensor pipeline correct, arrives in the viewer, and the character's right arm is raised on its left side. Nothing is rotated. Nothing is scaled. It is reflected. And no amount of adding 90° anywhere will fix it, because no rotation can turn a left hand into a right hand.
The usual next move is to start negating axes until it looks right. That works about a third of the time, which is worse than useless — it means the wrong fix survives. So let's find the actual invariant.
Almost every wrong-looking skeleton we ship is one of four things, and they have nothing in common but the symptom. Naming the class first is what stops you from applying a reflection fix to a multiply-order bug:
| Class | What went wrong |
|---|---|
| Reflection | A handedness conversion is missing, or applied to positions but not rotations. The pose is mirrored. |
| Wrong basis | A rotation is measured against a frame you did not mean — a rest pose, a bind pose, a sensor-to-segment offset. One bone goes wrong; its parent is fine. |
| Wrong multiply order | Local and world confused, or a correction composed on the wrong side. Error compounds down the hierarchy: root fine, fingers wild. |
| Accumulating error | Drift. Not a convention bug at all — normalization and reference-frame decay. It gets worse over time, which none of the other three do. |
That observation is chirality: whether the body's right hand is still genuinely its right hand. It is worth learning first because it is the only one of the four you can settle with a single glance and no history. A reflection is visible in one frame. The other three are not: wrong-basis and multiply-order bugs need you to compare a joint against its parent, and drift needs you to compare now against a minute ago. So if the pose is mirrored, it is the first class; and if it is not mirrored, you have ruled that class out and can stop permuting signs.
A basis is three direction vectors you agree to measure everything against.
Coordinates are just recipes: (3, 1, 4) means three of the first direction, one
of the second, four of the third. The numbers alone say nothing about where the point is —
they only mean something once you know the basis. Practically every convention bug in your
pipeline is a number that got read against the wrong basis.
Handedness is one bit of information about a basis, and it is not about which axis is up. Eric Lengyel states the test cleanly: the two arrangements
are called left-handed and right-handed because the positive z axis points in the direction of the left or right thumb when the fingers of the same hand curl around the z axis in the direction from the positive x axis to the positive y axis. — Foundations of Game Engine Development, Vol. 1: Mathematics, §1.2
So: sweep from X toward Y. Whichever thumb follows that sweep and ends up pointing along Z names the system. Drag the two figures below and find the one difference between them.
That is the entire difference. One axis points the other way. Now watch what that costs you.
Both systems make two promises about what their axes mean for a character, and the promises are the same. Here are the primary sources.
glTF uses a right-handed coordinate system.
glTF defines +Y as up; the front side of a glTF asset faces +Z, the left side of a glTF
asset faces +X.
The units for all linear distances are meters.
All angles are in radians.
Unity uses a left-handed coordinate system … the positive x-axis points to the right, the
positive y-axis points up, and the positive z-axis points forward.
Unity's manual is terser than the glTF spec here: it pins the axes and the handedness, and that is all this lesson needs from it. Where the reference sheet lists a Unity row with no quotable sentence behind it — units, angle units, multiply order — it says so. docs.unity3d.com
Read those two side by side and the collision is right there. Both say up is +Y. Both say the character's forward is +Z. But glTF says +X is the asset's left, and Unity says +X is its right.
Two systems that agree on two axes and differ in handedness cannot agree on the third.
Handedness is a property of the three axes taken together. Fix up and fix forward, and the last
axis has no freedom left — its direction is forced by the handedness. Unity and glTF both chose
up and forward the same way, so the disagreement had nowhere to go but
X. That is not an inconsistency to be worked around. It is the
conversion, and the spec sentence the left side of a glTF asset faces +X
is where it is
written down.
Below, the same pose is authored in Unity on the left and copied into glTF on the right. Choose what to do to the coordinates on the way across. The body is asymmetric on purpose — a symmetric T-pose looks perfectly fine mirrored, which is exactly how these bugs reach production.
Before you touch it, predict: three of the four options reverse handedness. Which one of those three also leaves the character facing the way glTF expects?
Negate Z passes the mirror check. The character is genuinely not reflected — its right hand is still its right hand. It is also facing backwards, because you preserved X and spent the handedness flip on Z, which is the axis that meant forward. It differs from the correct answer by exactly 180° of yaw, so it looks plausible from the front and wrong the instant anything turns around. Negate X is the only option that reverses handedness and keeps every semantic promise glTF makes.
Neither check up there is hard-coded — both are computed from the pose, so they cannot quietly disagree with this text. They are worth doing by hand once, because they are the two questions you will actually be asking of a real pipeline.
Is it reflected? Take the body's three semantic directions — forward, up, and its own right — and compute the triple product:
Check it once on a body facing you in a right-handed frame: forward is +Z,
up is +Y, and because it faces you, its right is your left,
−X. Then (0,0,1) × (0,1,0) = (−1,0,0), which is its
right, so the product is +1. Any real body gives +1 here; a mirrored one
gives −1. Two things to hold onto: the sign depends on the handedness of the frame you
measure in — the same correct body reads −1 in a left-handed frame like Unity's — so
compare against a pose you know is right in the same frame. And it is a direction test:
it catches mirroring, not wrong limb lengths.
And this is why no rotation can fix a mirror. That triple product is exactly the
determinant of the matrix whose columns are forward, up, and right. Apply any linear map
A to all three and the determinant is multiplied by det A — that is what
a determinant is. A rotation has det = +1, so it leaves the sign alone, every time,
for every rotation. A reflection has det = −1. A sign that only
det < 0 can change cannot be repaired by anything with
det > 0, and that closes off the entire "add a rotation somewhere" family in one
line.
Does it honour the target's axes? The second check is different in kind: it compares the body's three semantic directions against what glTF declares its axes mean (+Y up, +Z front, +X the asset's left). This is the stricter of the two — passing it implies not-mirrored — and it is the one that separates negate X from negate Z, because both are unmirrored and only one faces the right way.
This is what the real importers do. Khronos's UnityGLTF applies a coordinate-space conversion
scale of (-1, 1, 1) on the way in and on the way out. It is its own inverse, so the
same line of code works in both directions:
// Unity ⇄ glTF. Same function both ways: negating X twice is identity.
static Vector3 FlipHandedness(Vector3 v) => new Vector3(-v.x, v.y, v.z);
Rotations need a partner rule, and it is not the obvious one — you do not negate the
quaternion's x. The reason positions get one M and rotations get
two is worth a sentence, because it is the whole reason the two rules end up negating
opposite components.
A position is a thing in the space, so converting it means converting it once:
p′ = M p. A rotation is not a thing in the space — it is an operation on the
space, a machine that eats a vector and returns a vector. To run that machine in the new
convention you have to translate on the way in and back on the way out: take the new-convention
vector back to the old one, rotate there, then bring the result forward again. That is
R′ = M R M⁻¹ — three steps, read right to left. And because our M is a
mirror, applying it twice is the identity (M² = I, so M⁻¹ = M), which is
why it is written M R M here and not M R M⁻¹.
What does that conjugation do to the rotation? A reflection reverses the sense of every rotation,
so M R M is a rotation by the negated angle about the reflected axis.
Writing q = (sin(θ⁄2)·n, cos(θ⁄2)) and substituting θ → −θ,
n → Mn, the two sign changes cancel on the component you flipped and survive on the
other two:
So the position rule and the rotation rule negate opposite components. If you take one thing from this lesson into your next debugging session, take that: a handedness conversion that negates the same letter in both places is wrong, and it is wrong in a way that looks fine until something rotates. Lesson 002 makes you feel that one directly.
Given a mirrored skeleton, you can say which axis is being negated and which one should be — from the spec, not from trial and error. And you can rule out the whole family of "add a rotation to fix it" attempts in one sentence: no rotation changes chirality.
Primary source, 4 minutes:
glTF 2.0
Specification § 3.4, “Coordinate System and Units”. It is four sentences long and it settles
every argument your team is going to have about +X. Read the
neighbouring § 3.5.3 on TRS while you are there — it is the subject of lesson 003.
The other side of the boundary, 2 minutes: Unity Manual — Quaternion and euler rotations in Unity. Shorter and less formal than the glTF spec, but it is the sentence to cite when someone claims Unity is right-handed. Note what it does not pin down — units, angle units, multiply order — because those are the rows on the reference sheet that carry no quotation.