Skip to content

Rasterization Primer

Rasterization maps geometric primitives from continuous space onto a discrete image grid. The central question is simple: for each pixel sample, decide whether a primitive covers that sample and then compute the value that should be written to the framebuffer.

Pipeline sketch

  1. Transform vertices from model space to clip space.
  2. Project and divide by $w$ to obtain normalized device coordinates.
  3. Convert normalized coordinates into screen-space samples.
  4. Test triangle coverage and interpolate attributes.
  5. Resolve visibility with a depth buffer.

The perspective projection can be summarized as

$$ p_{clip} = P V M p_{model} $$

where $M$, $V$, and $P$ are the model, view, and projection matrices.

Barycentric coordinates

For a triangle with vertices $a$, $b$, and $c$, a point $p$ on the same plane can be written as

$$ p = \alpha a + \beta b + \gamma c,\quad \alpha + \beta + \gamma = 1. $$

Inside-triangle tests usually check whether all three barycentric coordinates are non-negative. The same coordinates also interpolate color, normal, texture coordinate, and depth.

ConceptPurpose
CoverageDecides whether a sample belongs to a primitive
InterpolationCarries vertex attributes to fragments
Depth testKeeps the closest visible fragment
cpp
Color shade(Fragment f) {
    Vec3 n = normalize(f.normal);
    Vec3 l = normalize(light.position - f.position);
    float diffuse = max(dot(n, l), 0.0f);
    return f.albedo * diffuse;
}

A clean rasterizer is a careful agreement between geometry, sampling, and numerical convention.