From e691f072aea2df026ea36d6b1839cef7e4787c71 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 12:37:24 +0300 Subject: [PATCH] render3d/shadow: tree impostors cast only into the far cascades Every tree on the map was drawn as an impostor card into all the cascades, the near two included, whose receivers (within 60 m) are shaded by the near trees' own LOD2 meshes cast beside them. The cards now cast from cascade 2 on. Aspen view, same main and foundations, 240 frames, with the figure capsule: shadow instances 323,587 -> 202,743; shadow GPU 4808-4942 -> 4579/4578 us; median frame 17200-17693 -> 17178/17205 us. The look held: shots of the view taken twice a side are identical within a side (0.00% of pixels past 8) and differ by 0.04% across (every pairing). Co-Authored-By: Claude Opus 5.5 --- packages/ludic.render3d/scatter.ludic | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 48d70f6a..fcbea5de 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -1058,6 +1058,7 @@ function scatter_draw(render3d_st: mut Render3dState) -> void { # whole class of caster vanish at a fixed distance, which is exactly the popping. A layer # with an impostor now casts its entire instance list from the card in every cascade; # only layers that have no impostor at all fall back to the mesh. +const SC_IMP_CAST_CASCADE: int = 2 function scatter_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { for i in 0 .. len(render3d_st.sc_layers) { let l = render3d_st.sc_layers[i] @@ -1067,7 +1068,10 @@ function scatter_draw_casters(render3d_st: mut Render3dState, light_vp: floats) if render3d_st.r3d_no_trees and l.imp != null and not l.card { continue } layer_update(render3d_st, l) if l.imp != null { - layer_draw_shadow(render3d_st, l, light_vp) + # the impostor casts only into the far cascades: every tree on the map was drawn as a card + # into the near two as well, whose receivers (within 60 m) are shaded by the near trees' + # own LOD2 meshes, cast just below + if render3d_st.sh_cascade >= SC_IMP_CAST_CASCADE { layer_draw_shadow(render3d_st, l, light_vp) } # Shadow LOD (the practice in every production engine: a caster uses a low mesh LOD, # the billboard only far away). The card alone is a side-view silhouette and a # crown of drooping needle cards is mostly slivers from the side, so the sun, which