Computer Graphics · Rasterization

Guard-Band Clipping and Why Vertices Are Cut Before the Divide

Clipping happens in homogeneous space because the perspective divide is undefined behind the eye, and a guard band lets most partially-visible triangles skip the clipper entirely.

A vertex positioned exactly at the eye has $w = 0$, and the perspective divide $x/w, y/w, z/w$ has no value there. A vertex behind the eye has $w < 0$, and the divide produces a coordinate reflected through the origin, which is geometrically meaningless: a triangle with one vertex behind the camera projects to a shape that is not the image of the triangle at all. The clipper exists to remove that case before any divide happens, and because it must operate on values that still have a $w$, it works in the four-component homogeneous space the vertex shader emits.

What clipping actually removes

The view frustum in clip space is the intersection of six half-spaces, expressed as $-w \le x \le w$, $-w \le y \le w$, and the two depth bounds. Each is a linear inequality in the four clip coordinates, which is the key property: the clip-space frustum is a convex polytope, and testing a vertex against it costs a handful of dot products with no division. The near plane, $-w \le z$ (or $0 \le z$ under the zero-to-one convention), is the one that matters most, because it is the boundary where $w$ crosses zero for geometry approaching the eye.

The output of clipping is a polygon, not necessarily a triangle. A triangle crossing one plane yields a triangle plus a quadrilateral, or just a quadrilateral, depending on how many vertices lie inside; against the full frustum the result can be a polygon with more vertices than the input. Sutherland and Hodgman’s algorithm handles this uniformly: walk the polygon’s edges, keep vertices that are inside, and emit one new intersection vertex wherever an edge crosses the boundary. A convex polygon clipped by one half-space gains at most one vertex, and repeating across planes of a convex polytope keeps the output convex.

Each new intersection vertex carries attributes interpolated along the edge. That interpolation is linear in clip space, parameterized by the distance ratio $t = d_0 / (d_0 - d_1)$ where the $d$ values are signed distances to the plane. Because the interpolation happens before the divide, the ratio is not the screen-space ratio, and the attribute values it produces are the ones that the later perspective-correct interpolation in the varying path expects.

/* Clip one edge against the plane dot(p, v) + d >= 0.
   Interpolates all four clip components linearly in the parameter t. */
static int clip_edge(const float plane[4], const float a[4], const float b[4],
                     float out_a[4], float out_b[4])
{
    float da = plane[0]*a[0] + plane[1]*a[1] + plane[2]*a[2] + plane[3]*a[3];
    float db = plane[0]*b[0] + plane[1]*b[1] + plane[2]*b[2] + plane[3]*b[3];
    if (da >= 0.0f && db >= 0.0f) { copy4(out_a, a); copy4(out_b, b); return 2; }
    if (da <  0.0f && db <  0.0f) return 0;
    float t = da / (da - db);              /* no divide-by-zero: signs differ */
    float p[4];
    for (int i = 0; i < 4; i++) p[i] = a[i] + t * (b[i] - a[i]);
    if (da >= 0.0f) { copy4(out_a, a); copy4(out_b, p); }
    else            { copy4(out_a, p); copy4(out_b, b); }
    return 2;
}

Clipping also produces a subtle correctness requirement: the clipped polygon’s edges lie exactly on the clip plane, so after the divide they land exactly on the viewport boundary. If the rasterizer’s top-left rule treats that boundary differently from the way the adjacent geometry across the screen edge is treated, a one-pixel line appears along the window border. Most implementations avoid this by ensuring that the clipped edge is collinear with the window edge within the subpixel grid, so the coverage test’s tie-break applies consistently.

The price of clipping everything

The clipper is a serial, variable-length stage. A triangle that needs clipping produces a different number of output vertices from one that does not, which makes it hard to pipeline and hostile to wide parallel execution. Worse, the common case in a game frame is that most triangles are entirely inside the frustum, and the vast majority of the rest are only slightly outside, cut by one screen edge. For those, clipping is pure overhead: the geometry between the triangle’s true extent and the screen edge is never rasterized anyway, because it falls outside the render target.

This is the observation behind the guard band. Widen the region that is accepted without clipping to a rectangle larger than the render target, and let the rasterizer’s own bounding-box and coverage tests discard the parts that fall outside the actual target. A triangle that extends past the left edge but stays within the guard band proceeds to triangle setup unchanged, and the setup stage clamps its bounding box to the target. The clipper then only handles triangles that cross far outside the band or that straddle the near plane.

The guard band does not remove clipping; it reclassifies. What must still be clipped is any geometry with $w$ at or below zero, because nothing downstream can divide by it or interpret the reflected result. The near plane is therefore a hard boundary regardless of band size, and the band applies to the four side planes and to the far plane only.

The band’s size is bounded by the fixed-point range of setup. Coordinates inside the band are quantized to the subpixel grid as described earlier, and a coordinate too large for the integer representation wraps catastrophically. Implementations choose a band whose extreme coordinates still quantize within the accumulator’s range, frequently several times the dimensions of the largest supported target rather than the current one, so that the band is a property of the hardware rather than of the current viewport.

Choosing an epsilon the clipper can defend

The near plane test is the one place where an exact comparison is dangerous. A vertex that should be exactly on the near plane may be a few units in the last place on either side after the vertex shader’s arithmetic, and the choice made there determines whether a sliver is clipped or kept. Keeping it produces a vertex with $w$ very close to zero, and the divide then amplifies every rounding error in $x$ and $y$ by a factor approaching infinity. The resulting coordinate can be far outside any guard band, and the triangle’s coverage becomes nonsense.

The usual remedy is to clip against a plane slightly in front of the true near plane, so that surviving geometry has a $w$ bounded away from zero. What the bound should be depends on the coordinate magnitude the rasterizer can represent, which is why this epsilon is a joint property of the clipper and the setup stage rather than a free parameter. A near-plane epsilon that is too aggressive clips geometry that should be visible, shrinking the near field; one that is too small reintroduces the overflow.

Clipping against the true near plane and then trusting the divide is a mistake that shows up only at grazing angles, where a nearly edge-on triangle has vertices near the camera and the projected coordinates explode. The artifact is a triangle that smears across the screen, drawn with attributes interpolated from a nonsense parameter, and it is easy to misdiagnose as a depth-precision problem.

Degenerate output and the cost of chaining

Chaining six plane tests produces output that can be degenerate in ways a clipper must tolerate. A triangle almost entirely outside one plane, with one vertex barely inside, clips to a very thin polygon whose area after quantization may be zero. Passing it to setup wastes a setup slot and produces no fragments; discarding it early on a quantized-area test saves both. Implementations differ in where they make that cut, and the choice affects how many triangles reach the conservative path, which must not discard thin geometry because its contract requires at least one fragment per covered pixel.

The second degeneracy is a clipped vertex that coincides with a kept one, producing a polygon with a zero-length edge. Sutherland-Hodgman handles this in principle by emitting both intersection points, but the resulting edge satisfies the coverage test with $E = 0$ everywhere along a degenerate span and can generate a single fragment with an interpolated parameter of dubious value. Since the fragment is interior to the original triangle, its attributes are still correct, so this case is usually cosmetic rather than wrong.

The third is the case the whole stage exists to prevent: a triangle with vertices on both sides of $w = 0$ along a line that passes through the eye. Its projection is not a bounded region, and clipping against the near plane returns a polygon whose vertices lie on the plane and whose edges extend to the clip boundary. This is correct output, but it means that a single input triangle can produce a polygon covering the entire viewport, and the rasterizer must be prepared for a triangle whose bounding box is the whole target. Guard-band clamping does nothing here; only the near-plane clip resolves it, and the resulting large triangle is precisely the case where fixed-point overflow in the edge function accumulators has to be considered.

What gets delegated to the scissor test

A separate mechanism, the scissor rectangle, performs a second rejection against an arbitrary sub-rectangle of the target after rasterization. It is cheap, it applies to the fragment rather than the primitive, and it composes with the guard band: the band keeps primitives alive through setup, and the scissor discards the fragments that fall outside the intended region. The division of labour is the whole design: clipping handles topological correctness, the guard band and bounding box handle geometric waste, and the scissor handles application-level region restriction, each at the stage where it is cheapest.

References

  1. Reentrant Polygon Clipping — Sutherland & Hodgman, Communications of the ACM, 1974
  2. A Trip Down the Graphics Pipeline — Blinn, Morgan Kaufmann
  3. Vulkan Specification — Clip Space and Clipping — Khronos Group