Course outline
Matrices Without Fear
In one line
A 4 × 4 matrix is a recipe that moves a point from one space to another. Translate, rotate and scale are each one matrix, they combine by multiplying, and the order you combine them in changes the result.
Think of it like this
A recipe card says "cut, then cook, then plate". Swap the order (plate, then cook) and you get a different dish. A matrix is a recipe for moving a point: "turn it, then slide it" is not the same as "slide it, then turn it". The matrix stores the whole recipe as 16 numbers, and the GPU applies it to every corner of a mesh at once.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- matrix
- A grid of numbers that, multiplied with a point, moves the point to another space. three.js uses 4 × 4 matrices (
Matrix4). - identity matrix
- The matrix that does nothing: 1s on the diagonal, 0s elsewhere. A new
Matrix4is the identity. - compose
matrix.compose(position, quaternion, scale)builds the matrix for "scale, then rotate, then translate" in one call.- matrixWorld
- An object's matrix combined with all of its parents': it takes a point from the object's own space to world space.
Step by step
01Three matrices
The first example makes one matrix for each kind of change, prints them, and sends the point (1, 0, 0) through each. Move by (2, 0, 0): the point becomes (3, 0, 0). Turn 90° around y: it becomes (0, 0, −1). Scale by 2: it becomes (2, 0, 0).
Look at the printed turn matrix: the first column is (0, 0, −1), where the x axis went, and the third column is (1, 0, 0), where z went. The columns of a rotation matrix are where the axes land.
02Two orders, two results
multiplyMatrices(move, turn) means turn first, then move: (1, 0, 0) becomes (0, 0, −1) and then (2, 0, −1). multiplyMatrices(turn, move) means move first, then turn: (1, 0, 0) becomes (3, 0, 0) and then (0, 0, −3). Same two pieces, different place. The order is where most hand-written matrix code goes wrong.
03compose
compose(position (2, 0, 0), a 90° turn, scale 2) applied to (1, 0, 0): scale gives (2, 0, 0), turn gives (0, 0, −2), move gives (2, 0, −2). The output shows (2, 0, −2). This one call is what every Object3D does behind the scenes.
04When matrices are up to date
In the second example the parent is moved to x = −1. Before any render, the child's matrixWorld x is still 0.00: nothing has computed it. After render() it is 0.50, which is −1 + 1.5. Then matrixAutoUpdate = false freezes the child: its local matrix stays at x = 1.50 even though position.x is 3, and the world x stays 0.50, until updateMatrix() rebuilds it (3.00 and 2.00).
Try it yourself
- 1
Move the parent further
In the second example, change
parent.position.x = -1;toparent.position.x = 2;. After the first render the child's world x is 3.50 (2 + 1.5). After the freeze andupdateMatrix()the numbers are 3.00 and 5.00, andmatrixWorld.elementsends with5, 0, 0, 1. - 2
Rotate around a different axis
In the first example, replace
makeRotationY(Math.PI / 2)withmakeRotationZ(Math.PI / 2). A 90° turn around z sends the point (1, 0, 0) to (0, 1, 0): the "turn" line prints0, 1, 0. Each axis has its ownmakeRotationX,makeRotationYandmakeRotationZ.
Code & diagrams
No picture is needed: this example is the numbers. Each matrix is printed row by row; the point (1, 0, 0) is sent through each.
Console
Run the scene to see its console.Expected console output
translate by (2, 0, 0):
[ 1 0 0 2 ]
[ 0 1 0 0 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]
rotate 90 degrees around y:
[ 0 0 1 0 ]
[ 0 1 0 0 ]
[ -1 0 0 0 ]
[ 0 0 0 1 ]
move: 3, 0, 0
turn: 0, 0, -1
grow: 2, 0, 0
turn, then move: 2, 0, -1
move, then turn: 0, 0, -3
compose(move, turn, grow) of (1, 0, 0): 2, 0, -2A blue parent at x = −1 carries an orange child at local x = 1.5. The console reports each state of the child's matrices.
Console
Run the scene to see its console.Expected console output
before any render: child.matrixWorld x = 0.00
after render(): child.matrixWorld x = 0.50 (-1 + 1.5)
frozen, position.x = 3: matrix x = 1.50, world x = 0.50
after updateMatrix(): matrix x = 3.00, world x = 2.00
matrixWorld.elements = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Degrees instead of radians
In the first example, change makeRotationY(Math.PI / 2) to makeRotationY(90).
Myth vs fact
Myth
You have to be good at matrix maths to use 3D.
Fact
You read three pieces (translation, rotation and scale) and use compose, multiply and matrixWorld. You almost never multiply numbers by hand. The one rule worth memorising is the order: scale, then rotate, then translate.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Object3D.updateMatrixWorld()walks the tree from the top and multiplies each child'smatrixby its parent'smatrixWorld.matrixWorldNeedsUpdateandmatrixAutoUpdatelet three.js skip unchanged branches. For thousands of static objects, setmatrixAutoUpdate = falseonce, or use instancing (Phase 17) which stores one matrix per instance in a buffer. - ▸
multiplyMatrices(a, b)computes a × b: apply b first.matrix.multiply(m)isthis = this × m, andpremultiply(m)isthis = m × this. Confusing these two is a classic bug; when unsure, print a test point.
Remember this
- 1
The problem this solves: every object in the scene has a position, a rotation and a scale, and every ancestor adds its own. Applying them one by one to each of a million corners would be slow, and hard to combine. One matrix per object holds the whole recipe, and matrices multiply together into one.
- 2
What a matrix looks like. A
Matrix4is a 4 × 4 grid of numbers. Do not try to read it as a puzzle. Read it in three pieces: the last column (the top three numbers of it) is the translation, the 3 × 3 block at the top left holds the rotation and scale, and the bottom row (0, 0, 0, 1) is the same for everything but a camera lens. Look at the first example's output: the translation matrix is the identity grid with the move (2, 0, 0) in the last column. - 3
Why four numbers for three dimensions. A 3 × 3 grid can rotate and scale but cannot slide, because sliding adds a number, and a 3 × 3 multiplication cannot add. A trick of homogeneous coordinates adds a 1 to every point, (x, y, z, 1), so one 4 × 4 multiplication can rotate, scale and slide at once. three.js hides the 1 for you.
- 4
Order matters.
A.multiplyMatrices(a, b)means "do b first, then a". Turn then move gives (2, 0, −1) for the point (1, 0, 0); move then turn gives (0, 0, −3). The standard order for an object is: scale, then rotate, then translate. That is whatcompose(position, quaternion, scale)builds, and what three.js stores inobject.matrix. - 5
**
matrixandmatrixWorld.**object.matrixis the object's local recipe, rebuilt from position, rotation and scale whenever they change.object.matrixWorldis the parent'smatrixWorldtimes the object'smatrix: the recipe from the object's space all the way to the world. Before the first render,matrixWorldhas not been calculated;renderer.render()(orupdateMatrixWorld()) fills it in.
Explain it without notes
Why does the order of translate and rotate matter, and in what order does three.js build an object's matrix?
A child's world position is wrong right after you move its parent. Give two possible reasons and how to check each.
Practice
Build a matrix that scales by 2, turns 90° around z, and moves by (1, 0, 0), using compose, and apply it to the point (1, 0, 0). Predict the result first.
Show with code that multiplyMatrices(a, b) and multiplyMatrices(b, a) can differ, for a a move and b a 90° turn.
Done when you can
Read the translation out of a matrix (elements 12, 13, 14)
Say which comes first in
multiplyMatrices(a, b), and in an object's own matrixExplain matrix, matrixWorld and matrixAutoUpdate in your own words
Open an object's matrixWorld in DevTools and say where it is