The simple idea that solved hidden surfaces: keep, per pixel, only the nearest fragment. A depth buffer starts at far; each incoming fragment writes its colour only if its depth is strictly less than what is stored, then updates the depth. Two overlapping triangles, rendered in either order, resolve to the same image. Rendered, not quoted.
source Catmull, E. E. — A Subdivision Algorithm for Computer Display of Curved Surfaces, Ph.D. thesis, University of Utah (1974). Full text: DTIC ADA004968. The z-buffer is disclosed there and in Catmull, Computer Display of Curved Surfaces (1975).
A framebuffer of colour C[x,y] and a parallel depth buffer Z[x,y], both indexed per pixel. Initialise Z ← +∞ (far), C ← empty.
For each fragment covering pixel (x,y) with depth z:
if z < Z[x,y]: C[x,y]←colour; Z[x,y]←z
Coverage is an edge-function test; interpolated depth is barycentric. The depth test is the only line that decides visibility — and it is a strict less-than.
Hidden surfaces solved by memory. Before 1974 the field sorted primitives back-to-front (the-painters-algorithm) — an O(n log n) sort that cannot resolve mutually overlapping or interpenetrating polygons.
Catmull's z-buffer spends one depth word per pixel and reduces hidden-surface removal to a local, per-fragment compare — no global sort. It sits immediately downstream of the-rasterization and is why the-painters-algorithm was retired for general scenes.
Live re-check of the running engine. It renders both draw orders and compares each to an independent min-depth reference. It confirms green — and flips red the instant the depth test is tampered.
Two overlapping triangles on a 16×16 raster.
NEAR = {(1,1),(13,2),(3,14)} · z=0.30 · yellow
FAR = {(4,4),(15,6),(6,15)} · z=0.70 · blue
They overlap. NEAR must win every overlapped pixel, regardless of the order the two are submitted.
Order shown: NEAR then FAR — flip it, the image is identical.
Every covered pixel holds the nearest fragment; both draw orders produce the byte-for-byte same image. Depth test proven strict; far fragments discarded.
"Z-fighting": two fragments at equal depth flicker between frames because the strict test is a coin-flip on quantised z. Real; mitigated by depth bias / higher precision, not solved here.
Depth precision is non-linear under perspective: a 24-bit buffer crushes almost all resolution near the far plane, so distant surfaces alias. The idea is exact; the storage is finite.
Transparency breaks it: the z-buffer keeps one fragment per pixel, so it cannot composite translucent layers without a separate sorted pass.
"You must sort polygons before drawing."
→ The z-buffer needs no sort; the per-pixel compare is order-independent.
"Painter's algorithm always works."
→ It fails on cyclic overlap and interpenetration; the z-buffer does not.
"The last triangle drawn wins."
→ The nearest wins. Draw order changes nothing in the output.
"Keep the fragment that is closer-or-equal (≤)."
→ The classic test is strict (<); equal depth is not overwritten.
Reverse the depth comparison — keep the farther fragment (z > Z). Now FAR geometry occludes NEAR: the far triangle wins the overlap and near surfaces vanish behind it.
Window 7 catches it live: nearest-fragment and reference-match both fail.