Coordinate spaces
A vertex is quoted in six different spaces on its way to the screen, and every matrix in the chain is just a change of address. The geometry never changes, only what the numbers are measured against. Almost every "invisible / inside-out / in the wrong place" bug is a vertex sitting in the wrong space.
The chain
model → world → view → clip → NDC → screen.
- Model matrix — puts a mesh built around its own origin into the shared world: position, rotation, scale.
- View matrix — moves the whole world so the camera sits at the origin looking down −z. It is the inverse of the camera's world transform: the camera never moves, the world moves around it.
- Projection matrix — maps the viewing volume into clip space and stashes depth in
w. - Perspective divide —
xyz ÷ w, done by hardware, giving normalised device coordinates with everything visible inside a −1…1 box. - Viewport transform — NDC to pixels, y flipped.
Why w exists
Perspective is a division, and matrices can't divide — so the matrix saves the divisor in w and the hardware does it afterwards.
Things look smaller with distance because screen position is x / distance. A 4×4 matrix can only add and scale, so the projection matrix copies the view depth into w and leaves it there; after clipping, the GPU divides. That is also why clipping happens before the divide, where the test is the tidy −w ≤ x ≤ w and nothing has yet blown up by dividing by zero.
Orthographic projection sets w = 1, so the divide does nothing and distance stops shrinking things — right for UI, maps and CAD views.
Near and far
Depth precision is spent near the camera, so a tight near plane costs you everywhere.
Depth after the divide goes as 1/z, so halving the near plane roughly halves usable precision at every other distance. Near 0.01 with far 10 000 is a recipe for z-fighting at mid range. Push near out as far as the game tolerates; the far plane barely matters by comparison.
The two silent bug factories
Handedness — right-handed (OpenGL-style, camera looks down −z) versus left-handed (D3D-style, +z forward). Mix conventions and geometry ends up behind the camera, or faces get culled inside-out.
Row- vs column-major — same sixteen floats, two different meanings. Column vectors multiply M * v and compose right-to-left (P * V * M * v); row vectors multiply v * M and compose left-to-right. Guessing wrong gives a transposed matrix: rotations become shears and translation lands in the wrong row.
Demo
The same two cubes in all six spaces — flip between view and NDC to watch the frustum straighten into a box, and read the tracked corner's numbers change line by line.
Gotchas
| Symptom | Cause |
|---|---|
| Object invisible, maths looks right | It is behind the camera (+z in a −z-forward world), or inside the near plane |
| Scene is a sheared mess | Row/column-major mismatch — transpose the upload |
| Everything drawn inside-out or culled | Handedness flipped, so winding reversed |
| Objects wobble or flicker at distance | Near plane far too small; depth precision exhausted |
| Lighting wrong only on scaled objects | Normals transformed by the model matrix instead of its inverse-transpose |
| Vertices explode near the camera | Dividing by w yourself, pre-clip, with w ≈ 0 |
| FOV change makes objects move, not zoom | Vertical and horizontal FOV confused, or aspect applied twice |