Skip to content

Renderer: shadow cluster cull uses the CAMERA frustum, not the light's (off-screen casters lose shadows) #652

Closed as not planned

Description

@drsnuggles8

VirtualGeometryShadow::RenderCascade (OloEngine/src/OloEngine/Renderer/VirtualGeometry/VirtualGeometryShadow.cpp) dispatches VirtualClusterCull.comp for each CSM cascade but never sets u_ViewProjection. The cull shader's 6-plane frustum test (VirtualClusterCull.comp:295-311) is not gated on u_OrthoMode, so it runs in the shadow pass too — using whatever view-projection is currently in the camera UBO (binding 0), which is the main CAMERA's.

Consequence: a cluster that is outside the camera view but inside the light's cascade — an off-screen shadow caster — is culled from the shadow map, so its shadow is missing from the visible frame.

Not visible in VirtualGeometryStress.olo (every dragon is on-screen), which is why it hid. Shows up when a caster is behind/beside the camera casting into view.

Fix sketch

Either upload the light's render-origin-relative VP (lightVPRel, already computed in RenderCascade for orthoErrorScale) into the camera UBO before the cull dispatch, or add a dedicated cull-frustum uniform the shadow path sets. Needs a test proving an off-screen VirtualMeshComponent still casts into the visible frame (the existing shadow tests all keep the caster on-screen).

Found while fixing #651.

capability: 3
craft: 5
stability: 5
decay: 2
effort: 3
confidence: 0.9
learning: 3
fun: 3
kano: must-be
blocked_by: []
blocks: []

Activity

  1. coderabbitai commented on Jul 15, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #126 - Feature/shadow_mapping [closed]
    #465 - Fix/flaky fog visual evidence test [merged]
    #538 - perf(renderer): early-out ShadowRenderPass when no light casts shadows (#522) [merged]
    #587 - Camera-relative rendering + absolute-world pattern shaders (#429) [merged]
    #628 - feat(renderer): clustered froxel lighting, budgeted shadow atlas, froxel volumetric fog (#435) [merged]


    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. added
    bugSomething isn't working
    robustnessProduction hardening / shipping robustness
    on Jul 15, 2026
  3. drsnuggles8 commented on Jul 15, 2026

    @drsnuggles8
    OwnerAuthor

    Could not reproduce — reverting the speculative fix and closing

    I built the fix (dedicated u_ShadowViewProjection for the ortho/shadow cull) and a test to prove it, and the test would not fail without the fix. Following the evidence rather than the hypothesis.

    What I verified, on a GPU, with the shader fix reverted and the shader cache fully nuked (confirmed real recompile), and with every classic mesh stripped from the scene so ONLY the virtual sphere can put depth in the CSM:

    • An off-screen shadow caster (a virtual sphere placed well above the camera's view frustum, inside the light's cascade, sun straight down) still casts its shadow correctly. The CSM contains its depth.

    So the described symptom — "off-screen casters lose their shadow" — does not occur. The geometry was sound (radius-1 sphere at y=8, camera frustum top ~y=5.6 at that depth, so it is genuinely outside the camera view), which points at the premise being wrong, not the setup.

    Why the premise was wrong (best current understanding): the frustum cull reads u_ViewProjection from the binding-0 camera UBO, but the shadow pass runs BEFORE ScenePass, and it appears the camera UBO is not populated with the camera's VP at that point (ScenePass uploads it later). So the shadow cull's frustum test operates on a stale/identity matrix and does not actually reject off-screen casters. Untidy — the shadow cull reading a camera-view UBO is wrong-by-construction — but with no observed effect.

    Action: reverted the fix (shader + VirtualGeometryShadow::RenderCascade + the test). I am not shipping a fix for a bug I cannot demonstrate, nor a test that passes with the fix reverted — that is exactly the vacuous-test trap the rest of this work fought.

    Closing as could-not-reproduce. If a future pass reorders the pipeline so binding-0 holds the camera VP during the shadow pass, this becomes a real latent bug and the dedicated-uniform fix (in the reverted diff) is the answer — worth a code comment at the cull's frustum section noting the dependency. Downgrading, not deleting the knowledge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrobustnessProduction hardening / shipping robustness

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions