AnyLearn
All lessons
Computer Scienceintermediate

From Scene to Pixel: The Transform Chain

Every renderer answers one question first: at this pixel, which surface is visible? Getting there means moving geometry through five coordinate spaces. This lesson builds that chain, explains why a fourth coordinate is not a trick, and shows where depth precision quietly goes wrong.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 9

The question underneath all rendering

A scene is a set of surfaces in three dimensions. An image is a grid of pixels. Rendering is the map between them, and it splits cleanly into two problems.

The first is visibility: for this pixel, which surface is seen? The second is shading: given that surface, what colour is it? Shading is where the physics lives, and the lesson Rendering as an Integral treats it as the integral equation it really is.

This cursus is about the first problem, because visibility is what forces the architecture. The two dominant renderer designs are two different answers to it, and almost every performance characteristic of a graphics system follows from which answer it picked.

Before either answer can be given, the geometry has to be expressed in a space where the question is easy to ask. That is what the transform chain is for.

Full lesson text

All 9 steps on one page, for reading, reference, and search.

Show

1. The question underneath all rendering

A scene is a set of surfaces in three dimensions. An image is a grid of pixels. Rendering is the map between them, and it splits cleanly into two problems.

The first is visibility: for this pixel, which surface is seen? The second is shading: given that surface, what colour is it? Shading is where the physics lives, and the lesson Rendering as an Integral treats it as the integral equation it really is.

This cursus is about the first problem, because visibility is what forces the architecture. The two dominant renderer designs are two different answers to it, and almost every performance characteristic of a graphics system follows from which answer it picked.

Before either answer can be given, the geometry has to be expressed in a space where the question is easy to ask. That is what the transform chain is for.

2. Five spaces, each earning its place

A vertex is authored once and then re-expressed several times on its way to the screen. Each space exists because some operation is trivial in it and awkward everywhere else.

Object space is where the model was built, with its own origin. World space places every object into one shared scene. View space re-anchors that scene on the camera, putting the eye at the origin looking down one axis, which is what makes the next step expressible at all. Clip space applies the projection. After dividing through, normalised device coordinates put everything visible inside a fixed cube. Screen space scales that to pixels.

The payoff of this staging is that each step is a matrix multiply, so the whole chain composes into a single matrix per object. A vertex is transformed once, not five times.

3. The chain, and where the divide happens

Everything up to clip space is a linear map, so those stages collapse into one matrix. The step that cannot collapse is the divide.

That asymmetry is the reason clip space exists as a named stage at all. Projection loads the fourth coordinate with the view-space depth, and the perspective effect is produced by dividing the other three by it. Division is not a linear operation, so it cannot be folded into the matrix product. It has to happen after.

Clipping is done before the divide rather than after, which is where the name comes from. Geometry behind the eye has a negative fourth coordinate, and dividing by it would fold those points back into view as though they were in front.

flowchart LR
A["Object space"] --> B["World space"]
B --> C["View space: eye at origin"]
C --> D["Clip space: w now holds depth"]
D --> E["Divide by w"]
E --> F["Normalised device coords"]
F --> G["Screen space: pixels"]

4. Why a fourth coordinate is not a trick

Points are carried as four numbers, (x, y, z, w), and the reason is structural rather than cosmetic.

Translation is not a linear map on three-dimensional vectors: no 3x3 matrix moves the origin, because a linear map always fixes it. Rotation and scale are linear, translation is not, so a chain mixing them cannot be one 3x3 product. Adding a fourth component with w = 1 embeds the points in a space where translation is linear, and a 4x4 matrix expresses all three uniformly.

The same extra component then does a second job. A perspective projection writes view-space depth into w, so the divide that follows scales x and y down in proportion to distance. That is the entire mechanism of perspective foreshortening: distant things are divided by a larger number.

5. The projection, in code

The matrix looks arbitrary until you watch what each row does.

import math

def perspective(fov_y_deg, aspect, near, far):
    f = 1.0 / math.tan(math.radians(fov_y_deg) / 2)
    return [
        [f / aspect, 0,  0,                              0],
        [0,          f,  0,                              0],
        [0,          0,  (far + near) / (near - far),   (2 * far * near) / (near - far)],
        [0,          0, -1,                              0],   # w = -z_view
    ]

def project(m, p):
    x, y, z, w = (sum(m[r][c] * p[c] for c in range(4)) for r in range(4))
    return (x / w, y / w, z / w)          # the perspective divide

M = perspective(60, 16 / 9, 0.1, 100.0)
project(M, (1, 1, -2, 1))     # near   -> roughly (0.49, 0.87, -0.90)
project(M, (1, 1, -20, 1))    # far    -> roughly (0.05, 0.09,  0.99)

The same object at ten times the distance lands ten times closer to the centre. The last row is the whole trick: it copies negated view depth into w.

6. Depth after the divide is not linear

The x and y results are intuitive. The depth value is not, and this is where a real bug lives.

Because z is divided by w, and w is view depth, the stored depth is proportional to 1/distance rather than to distance. Run the numbers from the example above: an object at 2 units maps to about -0.90, and at 20 units to about 0.99. Ninety percent of the available depth range was consumed by the first tenth of the scene.

The consequence is uneven precision. Near the camera, depth values are finely separated. Far away they crowd together, so two surfaces a metre apart can round to the same stored value and flicker against each other as the camera moves. That is z-fighting.

The standard mitigations are to push the near plane out as far as the scene allows, and to use a floating-point depth buffer with the range reversed, which places the dense floating-point values where the crowding happens.

7. What the chain has and has not settled

SpaceOriginWhat it makes easy
ObjectThe model's ownAuthoring and reuse of one mesh
WorldThe scene'sPlacing objects relative to each other
ViewThe cameraExpressing the projection at all
ClipThe cameraClipping before the divide
NDCCentre of the viewA fixed cube, resolution independent
ScreenA screen cornerAddressing pixels

After this chain, every visible surface is described in a common cube, and each point carries a depth. The projection has been solved.

Visibility has not. Two surfaces can still land on the same pixel, and nothing so far decides which one wins. The next two lessons are the two ways to decide, and they differ in a single architectural choice: whether to walk the geometry and see which pixels it covers, or walk the pixels and see which geometry they hit.

8. Why the split is drawn here

It is worth noticing how much has been made cheap before any renderer-specific work begins.

The transform chain is per vertex, and a vertex is transformed by one matrix multiply regardless of how many pixels the triangle it belongs to eventually covers. A model with 50,000 vertices costs 50,000 small matrix products, whether it fills the screen or occupies four pixels.

That independence is what allows the same geometry front end to serve both architectures. Rasterisers and ray tracers disagree about visibility, not about projection, and hybrid systems exploit exactly that: the geometry is prepared once and then queried two different ways.

It also explains a common performance surprise. Adding vertices costs vertex work, adding screen coverage costs pixel work, and the two budgets are separate. A scene can be bound by either, and the fix for one does nothing for the other.

9. The gotchas worth carrying forward

Four things in this chain break silently rather than loudly.

  • Order of multiplication. The chain composes in a fixed order, and swapping two matrices produces a plausible-looking scene that is subtly wrong. Rotation then translation is not translation then rotation.
  • Non-uniform scale and normals. Scaling an object unevenly and transforming its normals with the same matrix tilts them off the surface. Normals need the inverse transpose.
  • The near plane is not free. Setting it very small to avoid clipping nearby geometry is exactly what destroys depth precision everywhere else.
  • Clipping happens before the divide for a reason. Geometry behind the eye has negative w, and dividing by it silently maps those points into the visible range.

Each of these produces an image rather than an error, which is why they cost time.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Why can the perspective divide not be folded into the transform matrix?
    • Because it must be done per pixel rather than per vertex
    • Because division is not a linear operation, so no matrix product expresses it
    • Because the divide happens on the CPU rather than the GPU
    • Because clip space uses a different number of dimensions
  2. What is the structural reason points carry a fourth coordinate?
    • It stores the pixel's alpha value through the pipeline
    • It allows a colour channel to be interpolated alongside position
    • Translation is not linear in three dimensions, so it cannot be a 3x3 matrix
    • It doubles the available floating-point precision
  3. Why does depth precision concentrate near the camera?
    • Depth buffers use fewer bits for distant geometry
    • Distant triangles are clipped before depth is written
    • The near plane is sampled at a higher rate
    • Stored depth is proportional to 1/distance, so most of the range is spent close to the eye
  4. Why is clipping performed in clip space rather than after the divide?
    • Geometry behind the eye has negative w, and dividing by it would map those points into the visible range
    • Clip space is the only space where triangles remain triangles
    • The divide is too slow to run on all vertices
    • Normalised device coordinates have no defined bounds
  5. A model has 50,000 vertices but covers only a few pixels on screen. What does the transform chain cost?
    • Almost nothing, since cost scales with pixels covered
    • 50,000 matrix products, because vertex work is independent of screen coverage
    • It depends on the depth buffer format
    • The cost is shared with the shading stage and cannot be separated

Related lessons

Programming
advanced

Undo: Reset, Revert, Restore, and Getting Work Back

Git's undo commands are notoriously confusing because they are usually memorised as recipes. Read against the object model they separate cleanly: each acts on a different one of the three places content lives. This lesson maps them, covers recovery from the accidents that feel unrecoverable, and states what genuinely cannot be undone.

8 steps·~12 min
Programming
advanced

Merge and Rebase: Two Answers to Divergence

Every operation so far moved a pointer. Merging is the first that has to decide something: two branches changed the same project and one result must come out. This lesson covers the three-way merge and the merge base it depends on, what a conflict actually is, and why rebase produces different commits rather than moving them.

8 steps·~12 min
Programming
advanced

Refs: A Branch Is a File Containing a Hash

Hashes are unusable by hand, so Git names them. A branch is not a copy of anything, not a directory, and not a container for commits: it is a forty-character file. Once that is clear, checkout, reset, detached HEAD and the reflog stop being separate concepts and become one operation on one kind of file.

8 steps·~12 min
Programming
intermediate

The Object Database: Git Is a Content-Addressed Store

Git is usually taught as a set of commands. Underneath it is a key-value store where the key is a hash of the content, and four object types built on that one idea. This lesson derives the hash by hand, shows what a commit actually contains, and explains why the design makes history tamper-evident.

8 steps·~12 min