The Rasterizer
From triangles to fragments
The Bridge Between Geometry and Pixels
The vertex shader outputs vertices in clip space—but screens display pixels. Something must convert abstract triangles into the discrete grid of colors you see. That something is the rasterizer.
Rasterization is the fixed-function stage that determines which pixels a triangle covers. For each covered pixel, the rasterizer generates a fragment—a candidate pixel with interpolated data from the triangle's vertices. These fragments then flow to the fragment shader for final color computation.
Understanding rasterization helps you reason about visual artifacts, performance characteristics, and why certain techniques work the way they do.
Triangle Assembly
Before rasterization can begin, vertices must be assembled into triangles. The vertex shader outputs individual vertices; the primitive assembly stage groups them according to the draw mode.
The most common mode is triangle list: every three consecutive vertices form one triangle. With an index buffer, the indices determine which vertices combine. This is how you turn raw vertex data into drawable geometry.
Interactive: Triangle Assembly
Hover over triangles or index groups to highlight. Triangle strips share vertices between adjacent triangles, reducing index count.
Other modes include triangle strip (each new vertex forms a triangle with the previous two, reducing index count) and triangle fan (all triangles share a common vertex, useful for convex polygons).
// In the render pipeline descriptor:
primitive: {
topology: "triangle-list", // or "triangle-strip"
stripIndexFormat: undefined,
}Once assembled, each triangle passes through the remaining pipeline stages as a unit.
Clipping
Not every triangle fits entirely within the view frustum. A triangle might be partially behind the camera, outside the screen bounds, or crossing the near/far planes. Clipping handles these cases by cutting triangles against the frustum boundaries.
When a triangle crosses a clipping plane, the clipper generates new vertices at the intersection points and may split one triangle into multiple smaller triangles—all guaranteed to lie within the visible region.
Interactive: Frustum Clipping
Drag the slider to move the triangle across the clip plane. Watch how vertices are added at intersection points.
Triangles entirely outside the frustum are discarded (culled). Triangles entirely inside pass through unchanged. Only triangles that straddle the boundary require actual clipping work.
This is why clip space has the range for x, y, and z coordinates (where is the homogeneous coordinate). The hardware clips against these planes, then performs the perspective divide (, , ) to produce normalized device coordinates (NDC).
The Rasterization Algorithm
With triangles clipped and projected to screen space, the rasterizer must determine which pixels each triangle covers. The classic approach is edge function testing.
For a triangle with vertices , , , we define three edge functions. A point is inside the triangle if and only if it lies on the correct side of all three edges. In mathematical terms, the cross product of the edge vector and the vector to has consistent sign for all edges.
Modern GPUs test many pixels in parallel, using the edge functions to efficiently evaluate coverage. They process pixels in blocks (typically 2×2 or larger), enabling efficient partial derivative calculation for texture filtering.
Interactive: Rasterization in Action
Watch the rasterizer test each pixel center against the triangle edges. Pixels whose centers are inside become fragments.
Watch how the rasterizer tests each pixel against the triangle. Pixels whose centers fall inside the triangle become fragments; those outside are skipped. The visual shows the discrete nature of this process—the clean triangle edges become stair-stepped pixels.
Interpolation
Each vertex carries data beyond position: colors, texture coordinates, normals. The rasterizer must interpolate these values across the triangle surface so each fragment receives the appropriate blended value.
This interpolation uses barycentric coordinates—weights that describe a point's position relative to the triangle's vertices. For a point inside triangle :
where and all weights are non-negative. These same weights interpolate any per-vertex attribute:
Interactive: Barycentric Interpolation
Drag the sample point inside the triangle. Barycentric weights determine how much each vertex color contributes.
Move the sample point within the triangle and watch how the barycentric weights determine the blended color. At each vertex, the weight for that vertex is 1 and the others are 0. At the centroid, all weights are equal (⅓ each).
Critically, WebGPU performs perspective-correct interpolation by default. In screen space, equal distances do not correspond to equal distances in world space (due to perspective foreshortening). The rasterizer accounts for this by interpolating values divided by , then dividing by the interpolated . This prevents textures from swimming unnaturally when viewed at angles.
Face Culling
Most 3D objects are closed surfaces—you should never see the inside of a cube or a character model. Drawing back-facing triangles wastes GPU time on pixels that will be overwritten anyway.
Face culling discards triangles based on their orientation relative to the camera. The GPU determines orientation by the winding order of vertices in screen space. If vertices appear clockwise, the triangle faces one way; counter-clockwise, it faces the other.
Interactive: Front/Back Face Culling
As the triangle rotates, its winding order flips between counter-clockwise (front) and clockwise (back). Culling discards triangles facing away.
By convention, counter-clockwise winding indicates a front-facing triangle (facing toward the camera). Clockwise winding indicates back-facing. You can configure which faces to cull:
// In the render pipeline descriptor:
primitive: {
topology: "triangle-list",
cullMode: "back", // "none", "front", or "back"
frontFace: "ccw", // "ccw" (counter-clockwise) or "cw" (clockwise)
}Culling half the triangles nearly doubles your effective throughput for closed meshes. The only cost is ensuring your models have consistent winding order—a standard expectation in 3D content pipelines.
Note that face culling happens after clipping. A triangle that gets clipped may change its apparent winding, so the clipping algorithm must preserve consistent orientation in the resulting fragments.
The Output: Fragments
The rasterizer's output is a stream of fragments. Each fragment represents a potential pixel update and carries:
- Screen position: integer pixel coordinates
- Depth: the interpolated z-value for depth testing
- Interpolated varyings: all the values the vertex shader outputted (colors, UVs, normals, etc.)
- Coverage information: which samples within the pixel are covered (relevant for multisampling)
A fragment is not yet a pixel. The fragment shader will compute its color, and subsequent stages (depth test, blending) determine whether it actually updates the framebuffer. A single pixel might receive multiple fragments from overlapping triangles—only the nearest visible one typically survives.
Performance Considerations
Rasterization is highly optimized in hardware, but understanding its behavior helps you make better decisions:
Triangle size matters. Very small triangles (covering just a few pixels) incur significant overhead per pixel relative to their area. Very large triangles covering most of the screen are efficient. Avoid generating millions of tiny triangles when larger ones would suffice.
Overdraw is costly. When multiple triangles cover the same pixel, the rasterizer generates multiple fragments for that location. While the depth test eventually discards all but one, each fragment still costs fragment shader execution. Sort opaque geometry front-to-back to maximize early-z rejection.
Edge cases add complexity. Triangles that clip against frustum boundaries, degenerate triangles (zero area), and triangles viewed edge-on all require special handling. The hardware manages this, but be aware that unusual geometry can sometimes cause unexpected behavior.
Key Takeaways
- Primitive assembly groups vertices into triangles based on the topology (triangle list, strip, etc.)
- Clipping cuts triangles against the view frustum, discarding invisible portions and generating new vertices at boundaries
- Rasterization determines which pixels each triangle covers using edge function tests
- Interpolation computes per-fragment values from vertex attributes using barycentric coordinates
- Perspective-correct interpolation prevents texture distortion by accounting for depth differences
- Face culling discards back-facing triangles, nearly doubling throughput for closed meshes
- Fragments are candidates for pixel updates, carrying position, depth, and interpolated varyings to the fragment shader