Basics, part 4

Where a matrix sends the arrows

A matrix is a small grid of numbers that turns, stretches and slants every point at once. Read it by its columns, multiply by hand and undo it.

  • Part 47 min dose
  • Beginner

A matrix is a small grid of numbers that turns, stretches and slants every point at once. You’ve already used one, because rot2 from the sine and cosine lesson builds a matrix that turns. Let’s see where a matrix sends things, then multiply one by hand, stack two and undo one.

Drag the arrow tips
Drag the tip of the x arrow or the tip of the y arrow, or press Tab to reach them and use the arrow keys

Turn on JavaScript to run this demo.

lib.hlsl and arrows.hlsl draw the demo above
Fig. 1

Drag the tip of either arrow and watch the grid and the F follow along. The numbers in the corner follow too, mint ones for the mint arrow and pink ones for the pink arrow.

Columns are where the arrows land

Here’s the one idea that makes matrices click. Picture two arrows, (1, 0) one step across and (0, 1) one step up. A matrix’s first column is where (1, 0) lands, and its second column is where (0, 1) lands:

M=[ixjxiyjy]M[xy]=x i+y j\begin{aligned} M &= \begin{bmatrix} \textcolor{#10916c}{i_x} & \textcolor{#d9468c}{j_x} \\ \textcolor{#10916c}{i_y} & \textcolor{#d9468c}{j_y} \end{bmatrix} \\ M \begin{bmatrix} x \\ y \end{bmatrix} &= x\,\textcolor{#10916c}{i} + y\,\textcolor{#d9468c}{j} \end{aligned}

ii is the mint column and jj the pink one. A point (x,y)(x, y) is xx steps across plus yy steps up. The matrix keeps the steps and trades the two arrows for ii and jj, so the point lands on xx of ii plus yy of jj. Every point of the grid and the F does the same, which is why they all follow when you drag. The numbers in the corner of the demo above are MM itself.

Rows times columns

Let’s work out mul(M, v), a matrix times a point, by hand. Take float2x2(1, 1, 2, 0) as MM and v=(2,1)v = (2, 1). HLSL fills a matrix row by row, so its rows are (1, 1) and (2, 0), and its columns are (1, 2) and (1, 0). I picked it because it isn’t symmetric, so a mix-up shows.

By rows, each row is a dot product with the point. (1,1)⋅(2,1)=3(1, 1) \cdot (2, 1) = 3 is how far across it lands, and (2,0)⋅(2,1)=4(2, 0) \cdot (2, 1) = 4 how far up. Mix up rows and columns and you’d get (4, 2) instead.

By columns, take the first column 2 times, then put the second column once on the end of it. Both ways land on (3, 4), and the figure can play either one.

A matrix times a point

4: The point lands on (3, 4).

M = float2x2(1, 1, 2, 0)v = (2, 1)
2 × (1, 2) + 1 × (1, 0) = (3, 4)

A small zoo

Let’s meet a few families you’ll see again and again. Each one is easiest to picture by where it sends the two arrows.

  • The identity, float2x2(1, 0, 0, 1), leaves both arrows where they are, so nothing moves.
  • A scale stretches or squashes each arrow along its own line.
  • A rotation swings both arrows round together. That’s rot2, which builds float2x2(c, -s, s, c) from the cosine c and the sine s of the angle. Its first column is (c, s), the point on the circle.
  • A shear keeps the first arrow and tips the second one sideways, so the F leans over.
  • A flip turns one arrow around, and the F faces the other way, as in a mirror.
Five kinds of matrix

Turn on JavaScript to run this demo.

Which matrix the F goes through.
How much of it. At 0 every one is the identity.0.50
zoo.hlsl draws the demo above
Fig. 2

Pick a Matrix, slide Amount from 0 to 1 and watch which of the four numbers change. Halfway through the flip, the mint arrow shrinks to nothing and the F is squashed flat onto the pink one.

Two make one

mul(A, B) is a matrix too. It does to a point what BB does first and AA does after, so you read it from right to left. That’s why the order matters. Stretching and then rotating usually isn’t the same as rotating and then stretching.

Columns help again when you multiply by hand. The first column of mul(A, B) is where (1, 0) ends up, so send it through BB and then through AA. Each column of the product is AA times one column of BB, the way Gilbert Strang’s book below explains it. Here AA is a quarter turn and BB stretches to twice the width. The turn takes the first column of BB, (2, 0), to (0, 2), and the second, (0, 1), to (-1, 0).

Two matrices make one

4: Together they make mul(A, B) = float2x2(0, -1, 2, 0).

A = float2x2(0, -1, 1, 0)B = float2x2(2, 0, 0, 1)
mul(A, (2, 0)) = (0, 2)mul(A, (0, 1)) = (-1, 0)
mul(A, B) = float2x2(0, -1, 2, 0)
Which goes first

Turn on JavaScript to run this demo.

Which of the two happens first.
How far it rotates, in degrees, anticlockwise.90
How many times as wide the stretch makes things.2.00
order.hlsl draws the demo above
Fig. 3

Switch First between Stretch and Rotation and watch the butter F land somewhere else. Then set Rotation to 180, and both orders agree, because a half turn only points every arrow the other way.

Undo

To go back you need the inverse, written M−1M^{-1}. A point put through MM and then M−1M^{-1} is back where it started. HLSL has transpose and determinant but no inverse, so my demos bring their own inverse2.

Not every matrix can be undone. The determinant says how much MM grows areas. At 2 they double, below 0 they also flip over, and at 0 everything is flattened onto a line, or even a point. Many points then share one spot, and nothing can tell them apart again. For a 2 by 2 matrix it’s det⁡M=ixjy−iyjx\det M = i_x j_y - i_y j_x.

The transpose swaps rows and columns. For a rotation it’s the inverse, because a rotation keeps both arrows 1 long and at a right angle. For a stretch or a shear it isn’t.

Undoing a matrix

Turn on JavaScript to run this demo.

Which matrix the F goes through.
How much of it.0.50
What comes after the matrix to bring the F back.
undo.hlsl draws the demo above
Fig. 4

Try Undo with on each Matrix and watch when the butter outline lands back on the pale F. Then push Squash to an Amount of 1. The determinant drops to 0, and with Inverse the butter outline is gone.

In practice

A rotation matrix turns around (0, 0), which is a corner of the UVs, because they usually run from 0 to 1. To rotate a texture, this line moves the middle to (0, 0), rotates, and moves it back:

float2 uv = mul(rot2(_Angle), i.uv - 0.5) + 0.5;

Three traps catch everyone once. First, * between two matrices isn’t the product. Microsoft’s HLSL docs call the result “a per-component multiply of the two matrices”, so it multiplies cell by cell. The product is mul:

float2x2 both = mul(A, B); // B first, then A
float2x2 cells = A * B;    // cell by cell, not a matrix product

Second, a constructor like float2x2(...) fills row by row, and M[0] reads a row. The columns are M._m00_m10 and M._m01_m11, where _m10 means row 1, column 0, counting from 0:

float2x2 M = float2x2(1.0, 1.0,
                      2.0, 0.0);
float2 xLands = M._m00_m10; // (1, 2): the first column
float2 yLands = M._m01_m11; // (1, 0): the second column
float2 topRow = M[0];       // (1, 1): a row, not a column

Third, mul(v, M) treats vv as a row, which is the same as mul(transpose(M), v). With the MM and vv from before, both give (4, 2), the mix-up from the rows section:

float2 a = mul(v, M);            // v as a row
float2 b = mul(transpose(M), v); // the same two numbers

Unity’s UnityObjectToWorldNormal does this on purpose. It returns normalize(mul(norm, (float3x3)unity_WorldToObject)) for normals, the arrows that point straight out of a surface, and the reason is a story for a later lesson.

Takeaways

  • A matrix’s columns are where (1, 0) and (0, 1) land, and every other point follows them.
  • mul(A, B) does BB first and AA after, and swapping them usually changes the answer.
  • The determinant says how a matrix grows areas, and at 0 there’s no way back. For a rotation, the transpose undoes it.

Read more

Search the lessons

Type any word from the lessons, like normalize or smoothstep. The start of a word is enough.