Geometry, depth, and transparency
Examine reverse-Z depth, GPU occlusion culling, draw caches, skinning, and translucent scene composition.
Asterune prepares separate draw paths for opaque surfaces, fading geometry, transmission, water, ordinary transparency, and viewmodels. Each path has different depth and composition requirements. The renderer reduces submission work through cached commands, instancing, and indirect draws while preserving the depth and motion data that later passes need.
Visibility lists and shadow casters
CPU visibility testing starts with hidden state and the view's culling mask. Viewmodel geometry enters a separate render group before world frustum and distance tests. World geometry uses bounding spheres against the camera's frustum and draw distance. The distance test measures from the camera to the sphere's nearest extent rather than its center.
Shadow casters are collected independently of visible world draws. A caster outside the camera frustum can still cast a shadow into the picture. The renderer also retains an opaque scene list for work such as probes rather than limiting that work to the final camera-visible list.
Material classification determines the composition path. Water and decals have explicit paths. In realistic lighting, a transmissive material enters the transmission list. Ordinary transparent and transmissive mesh lists are sorted back to front by center distance. Opaque level-of-detail (LOD) transitions use a fading list so both geometry levels can contribute complementary coverage.
Cached commands and instance streams
A render bundle caches commands, bindings, and geometry references. Replaying one avoids recording every individual draw again. The opaque bundle cache matches mesh identities, geometry references, and chosen shader variants. Changing uniform contents does not by itself require different draw commands; changing geometry or the selected pipeline can require a new bundle.
Static world bundles group instances into spatial cells. Their cached opaque commands depend on the bundle generation, visible cells, and shader variants. Opaque and depth caches use separate entries for each eye and environment. A left-eye visibility difference therefore does not replace the right-eye cache on every stereo frame.
Dynamic instancing groups repeated geometry with compatible textures and shading needs. Current instance data and previous model matrices travel in separate streams. Moving an instance therefore retains object motion for temporal anti-aliasing (TAA). Membership and upload revisions are tracked separately: a changed transform can refill data without rebuilding the same grouping.
The multi-draw path handles eligible opaque meshes through a shared geometry arena and indirect commands. Its preparation checks GPU capabilities, static shadow eligibility, texture compatibility, and copyable geometry buffers. It requires at least 128 candidate meshes. These conditions keep a small draw list on the individual or instanced path rather than assuming every scene benefits from multi-draw preparation.
Shader specialization is another independent optimization. Draw groups carry their material shading needs, so a plain group does not require the surface-map or material-layer code used by another group. The frame's available pipeline remains the fallback while a specialized variant compiles.
Reverse-Z depth and coverage
Scene depth uses Depth32Float, clears to 0, and treats greater values as
nearer. Finite and infinite perspective projections follow this reverse-Z
convention. A near-plane sample has depth near 1; distant geometry approaches
0. Code that reduces or compares depth must preserve this direction.
The depth prepass runs when the screen-space plan requests depth. It draws individual opaque meshes, instanced and multi-draw geometry, fading geometry, terrain, and eligible static cells. A cached render bundle avoids rebuilding unchanged individual depth commands. If no prepass is needed, opaque shading establishes the scene depth itself.
Depth coverage must match color coverage. Alpha-masked surfaces apply the same texture-alpha cutoff in depth and color evaluation. LOD transitions use complementary dither tests in both passes. Without matching coverage, discarded color fragments could leave depth behind and incorrectly occlude later draws or screen-space lighting.
Viewmodels use a separate depth attachment, cleared immediately before their opaque draws. They render after world translucency and before temporal resolve. This gives viewmodel geometry its own depth ordering. Opaque viewmodels add color and motion to the single-sample scene. Transparent viewmodels add color while testing against the same viewmodel depth.
Early and late GPU occlusion culling
GPU culling operates on eligible static world bundles when compute and indirect execution are available. The frame also requires a single-sample scene, the GPU culling path to be enabled, and a screen-space plan that requests depth. Other frames use CPU-selected cells and their draw commands.
The GPU path divides visibility work into 2 phases. The early phase takes instances marked visible by the preceding cull and applies the current frustum and distance tests. It appends surviving instances to phase 0 output. Those instances contribute to the first depth prepass together with other scene geometry.
The late phase builds an occlusion hierarchy from that depth and tests the static instances again. An instance that passes visibility but was absent from phase 0 enters phase 1. A second depth pass draws those newly visible instances. Opaque shading then draws both phases. Updating the visibility mask prepares the next frame's early phase.
A newly created visibility mask starts empty. The late phase supplies the initial visible instances rather than relying on an earlier picture. An instance drawn early can remain in that frame's output even if the late test finds it occluded. That conservative extra draw avoids removing geometry based only on an outdated visibility prediction.
For each test, the shader projects the 8 corners of a box around the bounding sphere. It derives a screen rectangle and the object's nearest reverse-Z depth. The rectangle receives a 1-pixel margin before the shader chooses a hierarchy level from its extent. The test examines the covered texels at that level. Bounds crossing behind the camera or reaching the near plane remain visible.
Occlusion requires the object's nearest depth to be smaller than the farthest occluder depth across the rectangle. In reverse-Z, this means the entire bound lies behind the sampled occluders. A clear-depth sample prevents that conclusion. The hierarchy retains conservative coverage instead of assuming that one near sample covers a whole region.
Compute shaders append visible instance records into per-group output ranges. A finalization dispatch converts counters into indexed indirect draw commands. The renderer consumes those GPU buffers directly; visibility counts do not need a CPU readback before drawing.
Two depth reductions with opposite purposes
The renderer has separate depth hierarchies for culling and screen-space work. Their reductions differ because they answer different questions:
| Hierarchy | Reduction in reverse-Z | Purpose |
|---|---|---|
| GPU occlusion | Minimum depth, retaining the farthest sample | Establish that every sampled region occludes an object's nearest bound |
| Screen depth | Maximum depth, retaining the nearest sample | Supply visible depth for screen-space lighting, tracing, and effects |
The occlusion hierarchy allocates up to 12 levels, bounded by the target size. The desktop screen-depth texture allocates up to 6 levels. A headset keeps only level 0 for its screen-space consumers. Both reduction shaders include the remaining edge texels when a source dimension is odd.
When copying multisampled scene depth into screen depth, the copy shader takes the maximum across samples. This keeps the nearest covered sample. That copy is a depth reduction; it is separate from averaging multisampled scene color.
Allocating depth mips does not establish that every tracing shader traverses them. The reflection and contact-ray implementations use level 0 with their own search and refinement. Their algorithms are described in Lighting and reflections.
Skinning and previous vertex positions
GPU skinning uploads rest vertices and bone weights once, sharing that source between clones. Each animated part supplies pose matrices. A compute dispatch writes the posed positions and normals into the vertex buffer that subsequent draws read. Each vertex combines up to 4 packed bone influences.
Before replacing a posed vertex, the shader stores its preceding position in a
rgba32float texture. The world shader can therefore compute deformation
motion in addition to object-transform motion. A first pose uses the resulting
position as its previous position, avoiding an invented displacement from
uninitialized data.
A part posed on the preceding frame receives one extra dispatch if no new pose arrives. That dispatch uses the same pose, bringing previous and current positions together. Otherwise, a stopped animation would keep reporting its last deformation as motion. Multiple pose submissions for one part before a render collapse to the last pose.
The GPU path checks compute dispatch and storage-buffer limits. The engine's CPU deformation path supplies geometry when GPU skinning is unavailable. Both paths remain separate from physics collision geometry.
Weighted transparency and scene transmission
Ordinary alpha blending uses back-to-front mesh sorting. Sorting whole meshes by center cannot establish a per-pixel order for intersecting geometry. When its resources and quality setting enable order-independent transparency (OIT), the renderer uses weighted accumulation for order-dependent alpha draws.
OIT writes 2 single-sample targets: an Rgba16Float accumulation image cleared
to zero and an R8Unorm revealage image cleared to one. Each fragment adds
premultiplied color and alpha with a coverage- and depth-dependent weight.
Revealage multiplies the remaining uncovered fraction by 1 - alpha.
The composite divides accumulated color by accumulated weighted alpha and uses
1 - revealage as opacity. This weighted approximation combines overlapping
fragments without storing and sorting every fragment. Additive and other
order-independent blend modes remain separate draws after composition.
OIT depth-tests against the scene without writing scene depth.
Refraction needs a different input: a picture of the scene before the transmissive surface is drawn. The renderer copies single-sample scene color, or resolves multisampled color, into the transmission source and builds its mip chain. The material shader projects a refracted exit point into that picture. Roughness changes its blur and sampling level. The source therefore contains the earlier scene, rather than a recursively updated image of every transmissive layer.
Water similarly captures scene color and resolves depth before surface shading. On the single-sample path, water also writes motion. Blended draws that do not write motion then use a separate pass. These attachment boundaries explain why adding water or transmission can create more passes even when the number of visible meshes changes only slightly.