Command Palette

Search for a command to run...

Hectal
Course outline
Topic 2.5Beginner ~20 min · step 5 of 7

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.

0/7 · 0%

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 Matrix4 is 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. 1

    Move the parent further

    In the second example, change parent.position.x = -1; to parent.position.x = 2;. After the first render the child's world x is 3.50 (2 + 1.5). After the freeze and updateMatrix() the numbers are 3.00 and 5.00, and matrixWorld.elements ends with 5, 0, 0, 1.

  2. 2

    Rotate around a different axis

    In the first example, replace makeRotationY(Math.PI / 2) with makeRotationZ(Math.PI / 2). A 90° turn around z sends the point (1, 0, 0) to (0, 1, 0): the "turn" line prints 0, 1, 0. Each axis has its own makeRotationX, makeRotationY and makeRotationZ.

Code & diagrams

Translate, rotate, scale and order New tab

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.

idle

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, -2
matrix, matrixWorld and matrixAutoUpdate New tab

A blue parent at x = −1 carries an orange child at local x = 1.5. The console reports each state of the child's matrices.

idle

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).

terminal
$ Run
── what you'll see ──
rotate 90 degrees around y:
[ -0.448 0 0.894 0 ]
[ 0 1 0 0 ]
[ -0.894 0 -0.448 0 ]
[ 0 0 0 1 ]
turn: -0.448, 0, -0.894

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's matrix by its parent's matrixWorld. matrixWorldNeedsUpdate and matrixAutoUpdate let three.js skip unchanged branches. For thousands of static objects, set matrixAutoUpdate = false once, 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) is this = this × m, and premultiply(m) is this = m × this. Confusing these two is a classic bug; when unsure, print a test point.

Remember this

  1. 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. 2

    What a matrix looks like. A Matrix4 is 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. 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. 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 what compose(position, quaternion, scale) builds, and what three.js stores in object.matrix.

  5. 5

    **matrix and matrixWorld.** object.matrix is the object's local recipe, rebuilt from position, rotation and scale whenever they change. object.matrixWorld is the parent's matrixWorld times the object's matrix: the recipe from the object's space all the way to the world. Before the first render, matrixWorld has not been calculated; renderer.render() (or updateMatrixWorld()) fills it in.

Explain it without notes

01

Why does the order of translate and rotate matter, and in what order does three.js build an object's matrix?

02

A child's world position is wrong right after you move its parent. Give two possible reasons and how to check each.

Practice

01

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.

02

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 matrix

  • Explain matrix, matrixWorld and matrixAutoUpdate in your own words

  • Open an object's matrixWorld in DevTools and say where it is