Depth Buffer - Aukio 3D
Table of Contents
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:
- 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).
- 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).