Feature: GPU occlusion culling (Hi-Z) in the compute cull pass #167

Closed
opened 2026-09-29 08:41:04 +00:00 by sky_line · 2 comments
Owner

The Vulkan renderer currently culls only by frustum (and distance/size) in clean_cull.comp. Everything inside the view frustum is drawn, even when it's completely hidden behind walls, terrain, or other buildings. In dense sims and interiors, most of the submitted geometry is hidden this way, and we pay for it in the depth prepass, pass1 and shadow passes (see #123: depth prepass + pass1 ~21 ms on a GTX 1080 Ti).

Proposal

Add GPU-driven occlusion culling to the existing compute cull, in the standard two-phase Hi-Z form:

  1. Hi-Z pyramid: after the depth prepass (or main depth), build a max-depth mip chain with a compute shader. Single-pass downsample (SPD-style) is preferred.
  2. Phase 1: cull each instance's bounds (sphere/AABB projected to screen, then the mip level chosen from its footprint) against the previous frame's Hi-Z, reprojected with the previous view-projection matrix. Draw the survivors and build this frame's Hi-Z.
  3. Phase 2: re-test the instances rejected in phase 1 against the current Hi-Z and draw any that became visible. This avoids popping on disocclusion and camera cuts without a frame of latency.
  4. Output: keep writing to the same multi-draw-indirect command/count buffers, so the draw path doesn't change.

Details / constraints

  • Transparent / alpha-blended geometry is tested against Hi-Z but never contributes occluders. Alpha-mask geometry should not be an occluder either, unless it's promoted deliberately.
  • Rigged / animated avatars: use conservative bounds (skeleton-based bounds already used for frustum cull), padded for animation.
  • Shadow cascades: frustum-only for now. Occlusion from the light's view is a possible follow-up (per-cascade Hi-Z).
  • Reflection probe captures / mirror views: frustum-only initially. Their own Hi-Z could follow later.
  • Camera cuts / teleports: skip phase 1 (treat everything as visible) when the previous-frame reprojection is invalid.
  • Toggle: put it behind a setting (e.g. RenderVKOcclusionCull) with a debug view/stat showing instances tested / occluded per phase, so wins and false-occlusion bugs are measurable.
  • Tier gate: the whole thing is compute + indirect count, so it fits the GPU-driven path on every tier that has drawIndirectCount.

Measure

Compare against a baseline on the dense test sims:

  • pass1 / prepass / shadow GPU times;
  • instances drawn;
  • no visible popping when turning quickly or moving through doorways.

Related: #123 (geometry cost), #98 (low-end hardware).

The Vulkan renderer currently culls only by frustum (and distance/size) in `clean_cull.comp`. Everything inside the view frustum is drawn, even when it's completely hidden behind walls, terrain, or other buildings. In dense sims and interiors, most of the submitted geometry is hidden this way, and we pay for it in the depth prepass, pass1 and shadow passes (see #123: depth prepass + pass1 ~21 ms on a GTX 1080 Ti). ## Proposal Add GPU-driven occlusion culling to the existing compute cull, in the standard two-phase Hi-Z form: 1. **Hi-Z pyramid:** after the depth prepass (or main depth), build a max-depth mip chain with a compute shader. Single-pass downsample (SPD-style) is preferred. 2. **Phase 1:** cull each instance's bounds (sphere/AABB projected to screen, then the mip level chosen from its footprint) against the *previous* frame's Hi-Z, reprojected with the previous view-projection matrix. Draw the survivors and build this frame's Hi-Z. 3. **Phase 2:** re-test the instances rejected in phase 1 against the *current* Hi-Z and draw any that became visible. This avoids popping on disocclusion and camera cuts without a frame of latency. 4. **Output:** keep writing to the same multi-draw-indirect command/count buffers, so the draw path doesn't change. ## Details / constraints - **Transparent / alpha-blended geometry** is tested against Hi-Z but never contributes occluders. Alpha-mask geometry should not be an occluder either, unless it's promoted deliberately. - **Rigged / animated avatars:** use conservative bounds (skeleton-based bounds already used for frustum cull), padded for animation. - **Shadow cascades:** frustum-only for now. Occlusion from the light's view is a possible follow-up (per-cascade Hi-Z). - **Reflection probe captures / mirror views:** frustum-only initially. Their own Hi-Z could follow later. - **Camera cuts / teleports:** skip phase 1 (treat everything as visible) when the previous-frame reprojection is invalid. - **Toggle:** put it behind a setting (e.g. `RenderVKOcclusionCull`) with a debug view/stat showing instances tested / occluded per phase, so wins and false-occlusion bugs are measurable. - **Tier gate:** the whole thing is compute + indirect count, so it fits the GPU-driven path on every tier that has `drawIndirectCount`. ## Measure Compare against a baseline on the dense test sims: - pass1 / prepass / shadow GPU times; - instances drawn; - no visible popping when turning quickly or moving through doorways. Related: #123 (geometry cost), #98 (low-end hardware).
Author
Owner

Status check (verification sweep): the feature is implemented and shipped (v0.12.0-beta.6 / v1.0.0-beta.1):

Still open: both are off by default ("until benchmarked"). The Measure section is outstanding: prepass / pass1 / shadow timings on the dense test sims and a no-popping check. Then decide the default.

Status check (verification sweep): the feature is implemented and shipped (v0.12.0-beta.6 / v1.0.0-beta.1): - Hi-Z from the pre-pass depth (09ada3cb38, ea5e528f0f); - standard two-phase occlusion of the main view, RenderVKOcclusion2Phase (4e1d35d404, 5d1c1b4775), with counters and GPU sub-marks. Still open: both are off by default ("until benchmarked"). The Measure section is outstanding: prepass / pass1 / shadow timings on the dense test sims and a no-popping check. Then decide the default.
sky_line added this to the Slipstream project 2026-10-06 15:04:15 +00:00
Author
Owner

Ticket audit 2026-10-07: done and shipped in 1.0.0 Beta 6. Hi-Z and two-phase occlusion, on by default (09ada3cb38, ea5e528f0f, 4e1d35d404, cf18746620). If you still see the problem on the current beta, please reopen or file a new report.

Ticket audit 2026-10-07: done and shipped in **1.0.0 Beta 6**. Hi-Z and two-phase occlusion, on by default (09ada3cb38, ea5e528f0f, 4e1d35d404, cf18746620). If you still see the problem on the current beta, please reopen or file a new report.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream#167
No description provided.