A modern GPU chews through billions of triangles every second, and for each one it
must answer the same tiny question over and over, a few million times a frame: which pixels does
this triangle cover, and what colour and depth do they get? Turning a clean geometric triangle —
three corners in space, already flattened onto the screen by
Unlike a
Take one directed edge of the triangle, running from a first corner
This is nothing more than a 2-D cross product of the edge vector with the vector from the edge start to the test point. Its sign is all that matters:
A triangle is just the region trapped between its three edges. So a point is inside the
triangle exactly when it lies on the correct side of all three edges at once — that is, when
the three edge functions
Below, one triangle corner is yours to move. Each shaded square is a pixel whose centre passed all three edge tests — precisely the set of pixels the GPU would switch on. Notice how the filled region never quite matches the smooth triangle: pixels are square, so edges come out as little staircases. That jaggedness is aliasing, and taming it is a whole subject of its own.
Take the triangle with corners
All three are negative — the signs agree — so
The three edge-function values aren't just sign flags — their magnitudes, divided by the
triangle's total (twice its area), are the barycentric coordinates
measuring how close the pixel is to each corner. This is a beautiful two-for-one deal: the very same
arithmetic that decided whether a pixel is covered also tells us how to blend the
three corners' data across the triangle. Colour, texture coordinates, surface normals — any per-vertex
value
Coverage tells us which pixels a triangle touches — but a scene has thousands of triangles, many
stacked in front of one another. When two triangles both cover the same pixel, which one do we see?
The nearer one. The z-buffer (depth buffer) solves this with breathtaking simplicity:
alongside the colour of every pixel, store its depth — how far that surface is from
the camera. Start every pixel's depth at
Triangles can arrive in any order and the picture still comes out right, because the buffer
always remembers the closest surface seen so far. It is essentially a running
Suppose a red triangle and a blue triangle both cover pixel
Two triangles that share an edge — as they do all over any mesh — both have a claim on the pixels
sitting exactly on that boundary, where an edge function is precisely
The fix is the top-left fill rule: a boundary pixel (edge function exactly zero) is awarded to a triangle only if it lies on that triangle's top or left edge. This half-open convention makes the ownership of every shared edge unambiguous, so each pixel is drawn by exactly one triangle — no gaps, no doubles. It's a tiny rule with an outsized effect on picture quality.
Depth is stored with finite precision, and (because of the perspective divide) that precision is spread very unevenly — most of the bits are spent up close to the camera, leaving far-away surfaces with only a few crumbs of resolution. When two surfaces are almost coplanar and far off, their computed depths can round to the same value, so the z-buffer can't decide which is in front. The result is z-fighting: a shimmering, flickering speckle where the two surfaces swap winner from frame to frame. The classic cures are to pull the camera's near plane out (don't waste precision on nothing), use a smarter depth encoding, or simply not place two walls in the exact same spot.