Depth Buffer - Aukio 3D

Table of Contents

<- Back to index

1. Per-pixel visibility

The engine resolves visibility with a depth buffer, not paint order. Every rasterized triangle carries a per-pixel depth quantity zw = 1/z (camera-space), interpolated linearly across each span — 1/z is affine in screen space, so it rides the same edge interpolators as the texture gradients. A fragment wins a pixel only where

zw > stored - margin * zw^2        (margin = 0 by default)

Default margin 0 is strict depth: the nearer fragment always wins, regardless of paint order, so overlapping depth ranges — a floor tile extending under furniture, a wall seen through a doorway — come out correct per pixel. A nonzero margin (= RenderingContext.DEPTH_MARGIN_DZ , =-Daukio.zbuffer.margin, world units) re-opens a tolerance window behind the stored depth for near-coplanar pairs; any nonzero window re-imports per-triangle sort errors into per-pixel occlusion, which is why the default is strict.

The depth buffer is allocated once per frame context (RenderingContext.depth, float per pixel) and cleared per tile together with the pixel buffer.

2. Two passes

RenderAggregator.paintSorted paints the sorted queue in two passes:

  1. Opaque pass — opaque-class triangles, iterated front-to-back (the queue is back-to-front, so reversed), depth test + depth write. Front-to-back order is a pure performance hint: hidden fragments die on the depth test before the texture fetch (early-z).
  2. Alpha pass — alpha-class triangles (translucent solid polygons, alpha-carrying textures, SDF text), iterated back-to-front in queue order, depth test but no depth write. Translucency never occludes, and overlapping translucent surfaces keep painter-coherent mutual order.

A shape's class comes from its paint color or texture: solid polygons with alpha = 255 are opaque, anything translucent is alpha-class; textured triangles are alpha-class when the texture has alpha or is an SDF mask.

3. Which shapes carry depth

  • TexturedTriangle — opaque or alpha class by texture.
  • SolidPolygon — depth-tested since 2026-09-17; opaque when its (possibly shaded) color is fully opaque, translucent otherwise. Near-plane-clipped quads fan-triangulate with depth like any other triangles.
  • LightmappedTriangle — a textured triangle, so the same rules.
  • Line, Billboard, GlowingPoint — no depth by design: they are 2D overlays (wireframes, markers, sprites) and always paint on top, in the alpha pass.

Because every occluder writes depth, scene code no longer needs any ordering structure: composites just fan-triangulate their polygons. (LightmappedCompositeShape exists only to wrap polygons as lightmap carriers for the GI system, not to order them.)

4. Hi-Z occlusion pyramid

After each successful paint, ViewPanel builds a Hi-Z pyramid from the depth buffer (gui/HiZPyramid): 8-pixel tiles pooled upward, each tile storing the minimum zw (farthest written depth — max-pooling would store the nearest occluder and wrongly cull geometry visible between near gaps). Next frame, TriangleMeshBlock projects its world AABB's 8 corners and, when the nearest corner is still behind the pyramid's stored depth, skips the whole block before any per-triangle work.

The test is conservative by construction (min-pooling plus sky pixels at -inf), so it never culls visible geometry; wrong culls under camera motion self-heal in one frame. Knobs: -Daukio.hiz.margin=0.02, kill switch -Daukio.hiz=false. Headless snapshots never build the pyramid, so golden renders are structurally unaffected. Stereo skips the test (the pyramid is mono).

5. Determinism and depth dumps

The renderer is bit-deterministic: same scene and camera give bit-identical pixels across runs, which is what the golden-image regression tests compare. Snapshot (the headless toolkit) supports -Daukio.zbuffer.dumpDepth=path.png to write a grayscale depth map alongside the color image — useful when hunting depth-window bugs (bisect those with -Daukio.zbuffer.margin=0).

Author: Svjatoslav

Created: 2026-10-09 pe 00:26

Validate