Z-Buffer Algorithm: Image-Space Hidden Surface Removal
The Z-buffer algorithm (also called the depth-buffer algorithm) is the most widely used hidden surface removal technique in modern computer graphics.
It is implemented in hardware on virtually every GPU manufactured today, and it powers the real-time rendering of virtually every 3D game, simulation, and interactive application.
Its appeal comes from its extraordinary simplicity: rather than trying to solve visibility analytically in 3D object space, it resolves visibility pixel-by-pixel by comparing depth values.
What is the Z-Buffer Algorithm?
The Z-buffer algorithm is an image-space method it operates on pixels after 3D surfaces have been projected onto the 2D screen.
It resolves the hidden surface problem by keeping track of the depth (Z-value) of the closest surface seen at each pixel so far.
As each polygon is processed, its depth is compared pixel-by-pixel with the stored depth if the new polygon's surface is closer, it replaces the existing one; otherwise, it is discarded.
The algorithm requires two buffers, both of size equal to the screen resolution (width × height):
- Z-buffer (Depth buffer): Stores the depth value (Z-coordinate) of the currently closest surface at each pixel. Initialized to the maximum possible depth value (e.g., +∞ or the far clipping plane distance).
- Frame buffer (Color buffer): Stores the color of the currently visible surface at each pixel. Initialized to the background color.
Depth (Z-Value) and Its Role
In the view coordinate system, after projection, depth is the Z-coordinate of a surface point measured along the viewing direction. For any pixel (x, y) on the screen, multiple surfaces may project to that same pixel from different depths.
The visible surface is the one with the smallest Z-value (closest to the viewer), assuming the viewer is at z=0 and objects are at positive z.
(In some conventions, particularly with OpenGL's normalized device coordinates, z is mapped to the range [-1, +1] or [0, 1] and comparisons are still done using "less than" for the nearest surface the fundamental logic remains the same regardless of coordinate convention.)
The Z-Buffer Algorithm Step by Step
Initialize:
For every pixel (x, y) in the frame buffer:
Z-buffer[x][y] = +∞ (very large depth)
Frame-buffer[x][y] = background_color
Process each polygon P in the scene:
For each pixel (x, y) covered by the projected polygon P:
Compute Z_new = depth of surface P at pixel (x, y)
If Z_new < Z-buffer[x][y]: ← new surface is closer
Z-buffer[x][y] = Z_new ← update depth buffer
Frame-buffer[x][y] = color of P at (x, y) ← update color
Else:
Discard — the existing surface at this pixel is closer
After all polygons are processed, the frame buffer contains the correct visible image and the Z-buffer holds the depth map of the scene.
Computing Depth Incrementally (Efficiency)
For a planar polygon, the Z-value at any point (x, y) on the projected surface can be computed from the polygon's plane equation:
Ax + By + Cz + D = 0
→ z = -(Ax + By + D) / CThis can be evaluated at every pixel, but that would require a division for every pixel. Instead, the Z-buffer algorithm exploits coherence: because the polygon is flat, Z changes linearly as x and y change. Moving one pixel to the right (incrementing x by 1), the z-value changes by a constant increment:
Δz (per step in x) = -A/C
Δz (per step in y) = -B/CThis allows Z-values to be computed incrementally using only additions once the starting Z at the left edge of each scan line is known, subsequent Z-values are obtained by simply adding ΔZ at each step.
This is far more efficient than recomputing Z from scratch at every pixel.
Depth Value Precision Integer Z-Buffer
In hardware implementations, Z-buffer values are stored as fixed-point integers (commonly 16-bit, 24-bit, or 32-bit). The continuous depth range [near, far] is mapped to an integer range (e.g., [0, 65535] for 16-bit).
More bits give finer depth resolution, reducing artifacts like Z-fighting (discussed in limitations below). Modern GPUs commonly use 24-bit or 32-bit depth buffers.
Properties and Advantages of the Z-Buffer
- Simplicity: The algorithm is conceptually very simple a comparison and conditional write per pixel.
- No sorting required: Unlike the Painter's Algorithm, polygons do not need to be sorted by depth before rendering. The Z-buffer handles arbitrary scene complexity in any order.
- Handles any scene complexity: The algorithm works correctly for scenes with any number of polygons, regardless of how they intersect or overlap.
- Hardware-friendly: The per-pixel comparison-and-write operation maps perfectly onto massively parallel GPU architectures, making real-time Z-buffering routine on modern hardware.
- Handles polygon penetration: If two polygons interpenetrate (slice through each other), Z-buffering handles this correctly because visibility is resolved per-pixel, not per-polygon.
- Order-independent: Polygons can be submitted for rendering in any order; the final image is always correct.
Limitations of the Z-Buffer
- Memory requirement: A full-resolution Z-buffer requires significant memory. At 1920×1080 resolution with a 24-bit depth buffer, the Z-buffer alone requires about 6 MB. At higher resolutions this grows proportionally.
- Z-fighting (depth aliasing): When two surfaces are very close in depth, their floating-point (or integer) Z-values become nearly equal, and small numerical errors cause pixels to switch between the two surfaces randomly, producing a flickering pattern called Z-fighting. This is particularly visible at large distances from the viewer (where the depth buffer's precision is effectively lower due to the non-linear mapping from 3D space). Solutions include using a 32-bit depth buffer, careful placement of near/far clipping planes, or reversed-Z techniques.
- No direct support for transparency: The basic Z-buffer only keeps the single closest opaque surface at each pixel. Transparent or semi-transparent surfaces require special handling (e.g., alpha blending, which requires rendering transparent objects in back-to-front order after opaque ones, or using the A-Buffer algorithm).
- Aliasing: The Z-buffer resolves visibility at individual pixels. Where a polygon edge crosses a pixel boundary, the edge appears jagged (aliasing). Techniques like multi-sample anti-aliasing (MSAA) address this by evaluating multiple Z-samples per pixel.
Conclusion
The Z-buffer algorithm is a masterpiece of practical algorithm design: a conceptually simple idea compare depths per pixel, keep the closest that scales effortlessly to scenes of arbitrary complexity and maps naturally to massively parallel GPU hardware.
Its limitations (Z-fighting, transparency, memory cost) are well-understood and have spawned important extensions like the A-Buffer and techniques like reversed-Z depth buffers.
Every modern 3D rendering pipeline from AAA game engines to 3D medical visualization relies on the Z-buffer as its core visibility resolution mechanism.
Was this article helpful?