Core Idea
A transformation matrix stores exactly one thing: where the basis vectors land. The first column is the new home of x̂, the second column the new home of ŷ — drag either column above and the entire grid, and everything living in it, follows. That’s the whole trick:
[ Xx Yx Zx ] column 1 → where x̂ goes
[ Xy Yy Zy ] column 2 → where ŷ goes
[ Xz Yz Zz ] column 3 → where ẑ goes
The identity matrix is just every basis vector still at home — ones on the diagonal, nothing moved.
Rotation, Scale, Shear: Column Placements
Press the presets above and watch the columns, not the grid: rotation swings both columns around the origin at length 1; scale stretches them along their own axes (the length of a column is that axis’s scale factor); shear tilts one column while the other stays. There is no separate machinery for each transform — only different places to put the columns.
The gold parallelogram is the image of the unit square, and its area is the determinant. Drag a column until the grid folds through itself: det < 0 means the space is mirrored (winding flips — the same flip that turns a front-face into a back-face), and det = 0 means the plane collapsed to a line, a transform that cannot be undone.
Multiplying Is Just Dot Products
Transforming a vector is one dot product per output component — each row against the vector:
out.x = dot(row0, v)
out.y = dot(row1, v)
out.z = dot(row2, v)
Matrix × matrix is the same idea tiled: element [i][j] is row i of the first dotted with column j of the second. Equivalently — and this is the readable version — the product’s columns are the first matrix applied to the second matrix’s columns.
Order Matters
Multiplication chains transforms, and it reads right-to-left: in T · R · S · v the vector is scaled first, rotated second, translated last. Chains don’t commute — the same two matrices in opposite order produce genuinely different transforms:
Rotate-then-scale squashes the rotated shape against the world axes and shears its angles; scale-then-rotate squashes first and rotates the result rigidly. This is the classic bug: scaling after translating multiplies the position too, and an object placed at (10, 0, 0) scaled by 2 teleports to (20, 0, 0). Scale while centred at the origin, then rotate, then translate — see transformations for the full TRS story.
16 Floats for a Million Vertices
The pipeline’s economy rests on this: the CPU composes Model, View, and Projection into one 4×4 and uploads 16 floats as a uniform, and the GPU applies them to every vertex in parallel:
gl.uniformMatrix4fv(u_mvp, false, mvp) // 16 floats, once
gl.drawArrays(gl.TRIANGLES, 0, count) // mvp × position, per vertex
Whether the mesh has 3 vertices or 3 million, the transform costs the same to send. (mat4 in GLSL, float4x4 in HLSL.)
What a 3×3 Can’t Do
Every transform above fixes the origin — dot products of rows with (0,0,0) are always zero — so pure matrices can rotate, scale, and shear but never translate. Graphics fixes this with a fourth coordinate: see homogeneous coordinates for how w smuggles translation (and later perspective) into matrix multiplication.
Connections
Multiplying by a matrix doesn’t move geometry — it re-expresses it in a new frame, which is the central insight of coordinate spaces; the Model, View, and Projection matrices are the three hops of that journey, and projection is the strange one whose matrix only stages a division. How translate, rotate, and scale compose per-frame into a model matrix lives in transformations, and the reason those 4×4s work at all is homogeneous coordinates.