Coordinate spaces (rendering pipeline)
The five coordinate spaces a vertex passes through between the scene and the screen, and the exact matrix or formula that moves it each direction. Ordered here to match the actual rendering pipeline: World → Camera → Clip → Screen → Pixel.
The five spaces, in rendering order
Step 1: World → Camera Space
View_Mat * P — the camera’s own world transform, inverted, so that after this the camera sits at the origin looking down a fixed axis. Reverse: View_Mat⁻¹ * P.

Why the order is Rᵀ * T⁻¹: the view matrix moves the world, not the camera. Placing the camera in the world is T * R; the view matrix undoes that, and inverting a product reverses the order:
So orientation ends up on the left. R is orthonormal, so R⁻¹ = Rᵀ — just write the camera axes as rows instead of columns. Multiplied out, the fourth column is -dot(axis, eye), not -eye, because the translation is expressed in the camera axes. See Matrix composition order (SRT) for the right-to-left rule this inverts.
Step 2: Camera → Clip Space
Proj_Mat * P — encodes the field of view and near/far planes, and sets up (clip-space w is just a copy of camera-space depth, carried along purely so the next step has something to divide by). Reverse: Proj_Mat⁻¹ * P. 透视矩阵其实是为了下一步透视除法做准备,他定义了我们眼前的视锥体,check pic2 below.
### What each row does
Each row produces one output component. Read them independently.
Rows 1 and 2: fit the field of view to the screen. , , with .
After the divide, a point sitting exactly on the edge of the view has lateral offset over depth equal to , the same ratio for every point on that edge line. So without scaling, the visible region after the divide is , which changes with FOV. Multiplying by pulls that edge to exactly , so whatever you can see fills NDC’s and the viewport transform can treat every camera the same way.
Narrow FOV means small , big , everything magnified. That’s zooming in.
= width / height. FOV is vertical. On a wide screen, NDC’s is spread over more pixels horizontally than vertically, so one NDC unit is physically longer in x. Dividing x by cancels that stretch; skip it and circles render as ellipses. With a square screen and it does nothing, which is why a bug here hides until the window isn’t square.
Row 3: prepare depth. . After the divide this becomes . and are the solution to two conditions, near plane lands on 0 and far plane lands on 1:
Without , every point would come out as and the depth buffer would have nothing to compare. is not linear in : with , the first half of the range already uses up 0 to 0.99. Precision piles up near the camera, which is why a tiny near plane causes z-fighting in the distance.
Row 4: park the divisor. copies camera-space into . A matrix can only multiply and add, it can’t divide, so it stores the number the next step needs to divide by. An ordinary transform has here and keeps ; moving that 1 one column left is the entire trick.
In glm (column-major) this is glm::vec4(0, 0, P, 1) passed as the third column: the 1 lands at m[2][3], which is row 4, column 3.
Worked example
FOV 45°, , , gives , , .
| Camera-space point | After matrix | After (NDC) |
|---|---|---|
Same lateral position, twice the depth. After the matrix, and are identical; only differs. After the divide, the far point is half the size. The matrix sets things up, the divide is what makes far things small.
Sign-convention note: some derivations write instead (right-handed view space, camera looking down , so visible points have negative and the negation keeps positive). Whether you need the negative sign depends on how your source already defines — check before assuming one form is wrong. See Clipping for where this actually comes from in the projection matrix.
Between this step and the next, clipping happens — triangles are cut against the frustum boundaries while still in clip space, before anything is divided. See Clipping for why this order is mandatory, not just convention.
Step 3: Clip → Screen Space (perspective divide)
The step that actually creates the perspective effect. Reverse: P *= wk. See Perspective-correct interpolation for why this divide distorts linearity in screen space, and how rasterization corrects for it when interpolating attributes. 透视除法只是想象把一个视锥体变成一个高度和宽度在[-1, 1],深度[0, 1]的盒子。
Step 4: Screen → Pixel Space (viewport transform)
Reverse:
Why y flips here: NDC’s y increases upward (math convention), pixel space’s y increases downward (row 0 is the top of the screen) — this is the one step where that flip is actually applied.
Gotcha
Rasterization (both coverage-test methods) operates in pixel space — the final destination of this whole pipeline, it needs actual pixel coordinates to compare against. But the w value from before the perspective divide (Step 3) has to be preserved and carried through anyway, because attribute interpolation (perspective-correct interpolation) needs it even though the coverage test itself doesn’t.