Skip to main content
← All posts

Part 11: 3D Graphics: Parallel & Perspective Projections & Hidden Surface Removal

Super AdminNov 5, 20264 min readArticle
Part 11: 3D Graphics: Parallel & Perspective Projections & Hidden Surface Removal

3D Rendering Pipeline & Visibility Determination

The leap from 2D plane drawings to three-dimensional visual worlds requires solving two fundamental mathematical problems: Projection (mapping 3D spatial points $(x, y, z)$ onto a 2D planar projection screen) and Hidden Surface Removal (determining which surfaces are occluded from the viewer's eye). In this capstone installment, we analyze Parallel vs Perspective Projections, derive perspective foreshortening and vanishing points, and dissect the industry-standard depth occlusion algorithms: Back-Face Culling and the Hardware Z-Buffer (Depth-Buffer) Algorithm.

1. Geometric Projections: Parallel vs. Perspective

Parallel (Orthographic) Projection

Projection lines (projectors) are parallel to one another. The Center of Projection (COP) is effectively at infinity.

  • Preserves relative object dimensions and parallel lines.
  • Zero perspective foreshortening.
  • Essential for architectural blueprints, engineering CAD drawings, and isometric tactical video games.

Perspective Projection

All projectors converge at a single finite point: the Center of Projection (COP), representing the human eye or camera lens.

  • Foreshortening: Distant objects appear progressively smaller in inverse proportion to distance $z$: $x' = \frac{d \cdot x}{z}, y' = \frac{d \cdot y}{z}$.
  • Parallel 3D lines converge toward Vanishing Points on the horizon.
  • Models biological human depth perception and photographic realism.

2. Hidden Surface Removal (Visible Surface Determination)

In complex 3D scenes, millions of polygons overlap in screen space. Drawing opaque objects in arbitrary order produces chaotic visual artifacts where background geometry overwrites foreground objects. Visibility determination algorithms resolve occlusion using two primary paradigms:

  • Object-Space Algorithms: Compare 3D geometric surfaces against each other in world space (e.g. Back-Face Culling, BSP Trees). Complexity scales as $\mathcal{O}(N^2)$ with polygon count $N$.
  • Image-Space Algorithms: Resolve visibility point-by-point at each pixel on the projection screen (e.g. Z-Buffer). Complexity scales as $\mathcal{O}(N \cdot \text{Pixels})$.

3. Back-Face Culling

In closed polyhedral meshes (such as 3D characters, vehicles, or terrain), the polygons facing away from the camera are physically occluded by the front surfaces of the same mesh.

Each triangular polygon possesses an outward-facing surface normal vector $\vec{N} = (A, B, C)$ computed via the cross product of its edges: $\vec{N} = (V_1 - V_0) \times (V_2 - V_0)$. Let $\vec{V}_{view}$ be the viewing direction vector pointing from the camera to the surface:

\vec{N} \cdot \vec{V}_{view} \le 0 \implies \text{Back-Face (Cull / Discard)}

Back-face culling is executed early in the geometry shader stage of GPU pipelines, instantly pruning approximately 50% of the scene's triangles before rasterization.

4. The Z-Buffer (Depth-Buffer) Algorithm

First formulated by Edwin Catmull in 1974, the Z-Buffer Algorithm is the universal hardware standard implemented in 100% of modern GPUs (DirectX, Vulkan, Metal, WebGPU).

The GPU maintains two concurrent 2D memory arrays matched to screen resolution ($W \times H$):

  1. Color Buffer (Framebuffer): Stores the color $(R, G, B, A)$ of the currently visible pixel.
  2. Depth Buffer (Z-Buffer): Stores the floating-point depth $z \in [0.0, 1.0]$ of the closest surface currently rendered at $(x, y)$.

Z-Buffer Algorithmic Workflow

text
1234567891011121314
// 1. Frame Initialization
for each pixel (x, y) in Screen:
    DepthBuffer[x][y] = 1.0; // Farthest possible depth (normalized)
    ColorBuffer[x][y] = BackgroundColor;

// 2. Rasterization of all Polygons
for each polygon P in Scene:
    for each fragment (x, y) generated during scan conversion:
        Calculate fragment depth: z_frag;
        if (z_frag < DepthBuffer[x][y]) {
            DepthBuffer[x][y] = z_frag;       // Update to new closer depth
            ColorBuffer[x][y] = ShadeFragment(P); // Overwrite pixel color
        }
        // else: fragment is occluded by existing closer object -> DISCARD

Advantages & The Z-Fighting Phenomenon

  • Order Independence: Polygons can be submitted to the GPU in arbitrary order; the depth buffer guarantees correct occlusion without sorting meshes.
  • Z-Fighting: When two surfaces share nearly identical depth coordinates ($z_1 \approx z_2$), floating-point precision limitations cause the depth test to alternate erratically between surfaces across adjacent pixels, resulting in flickering visual noise. Modern engines mitigate this using reversed-Z floating point buffers ($[1.0 \to 0.0]$ mapping).
Share:
Share on X
Share on LinkedIn
Share on Facebook
Share on Reddit
Share on WhatsApp
Share on Telegram
Copy link

Related reading