Gabriel O’Flaherty-Chan spent his early years on frameworks that handed him a Camera that “just kind of worked” — and admits he lacked the vocabulary to even search for the camera behaviour he wanted until he wrote lower-level graphics code. So he wrote the missing explainer, with interactive shader examples you can drag.

The whole thing starts from one line:

  • x' = x / z, y' = y / z — with y up and z forward, that is a 3D point projected into 2D
  • Points that differ only in depth march toward the vanishing point at (0,0): (2,1,2) → (1,0.5), (2,1,4) → (0.5,0.25), (2,1,8) → (0.25,0.125)
  • The same division handles size: radius = 0.5 / z is why the demo ball shrinks as it orbits away
  • It scales to “geometry” — a rotating cube is 12 line segments, each with both endpoints projected the same way

Then the honest part. The naive trick only works for camera-relative points in simple scenes. Once you need camera position, direction and field of view, the practical answer is the perspective projection matrix — parameterized by FOV, aspect ratio, and near/far clipping planes, which double as the test for what is in frame and what can be culled:

  • f = 1 / tan(θ/2), with a = width / height
  • Depth mapping A = (F+n)/(F−n), B = −2Fn/(F−n)
  • After the matrix multiply and the divide by w, you get x_ndc = (f/a)·x/z and y_ndc = f·y/z
  • Set f = 1 and a = 1 and you are back at x/z — the trick was the matrix all along, just with the terms stripped out

Naming the stages is most of the value: world space → view space → clip space → divide by w → NDC → pixels → rasterization. NDC coordinates no longer mean distances; they mean where the point lands inside the camera’s visible bounds.

The takeaway is the one the author wanted when he started: a camera is a handful of transformations, and once you can name them you can implement only the parts you need. Sometimes that is the full pipeline. Sometimes it is one division.