ludic/packages/ludic.render3d/fog_casters.ludic
Orkuncakilkaya 329439cf61 render3d: under a fog wall the volumetric air is marched only to it, SSGI stops where it is solid, and card casters that fill the wall stop rebuilding
The volumetric pass marched 800 m in a fixed number of steps whatever the wall: it now stops at the
wall with steps in proportion (at least 8) - 0.57 -> 0.33 ms at 30 m by the pass timers. SSGI skipped
only past 900 m; it now also skips past 0.85 of a wall, where the fog is solid (u_ao_far). applyFog
samples the fog's colour only where its weight is above zero. A card layer whose casters inside the
wall are 80% or more of it (a kilometre's wall) casts from its static list and is not refiltered and
re-uploaded every 4 m of camera motion. Fog off: the frame unchanged (0 pixels over 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:00:27 +03:00

47 lines
2 KiB
Text

# fog_casters.ludic — under a fog wall, a layer's cards cast shadows only from where they can be seen.
# The impostor shadow list is every instance on the map; past the wall that is a vertex bill for
# nothing, so the casters inside the wall (and a shadow's throw past it) go into a list of their own.
const FOG_CAST_THROW: float = 30.0 # the longest shadow a tree throws back toward the camera
const FOG_CAST_STEP: float = 4.0 # the camera moves this far before the list is rebuilt
# the buffer and count the card shadows draw from: the whole list, or the wall's share of it
function fog_casters(render3d_st: mut Render3dState, l: Layer) -> int {
let w = render3d_st.r3d_fog_wall
if w <= 0.0 { return l.sh_buf }
let dx = render3d_st.cam_pos[0] - l.fog_x
let dz = render3d_st.cam_pos[2] - l.fog_z
if l.fog_wall == w and l.fog_src == l.n_sh and (l.fog_all or dx * dx + dz * dz < FOG_CAST_STEP * FOG_CAST_STEP) {
if l.fog_all { return l.sh_buf }
return l.fog_buf
}
l.fog_wall = w; l.fog_src = l.n_sh
l.fog_x = render3d_st.cam_pos[0]; l.fog_z = render3d_st.cam_pos[2]
let far = w + R3D_FOG_MARGIN + FOG_CAST_THROW + FOG_CAST_STEP
let far2 = far * far
let src = l.inst
let dst = l.scratch
var n = 0
let total = Math.min(l.n_sh, l.count)
for i in 0 .. total {
let o = i * INST_FLOATS
let ex = src[o] - l.fog_x
let ez = src[o + 2] - l.fog_z
if ex * ex + ez * ez > far2 { continue }
let q = n * INST_FLOATS
for k in 0 .. INST_FLOATS { dst[q + k] = src[o + k] }
n += 1
}
l.fog_n = n
# a wall that keeps nearly the whole layer (a kilometre): the static list, and no rebuild as the camera
# moves - copying and uploading 60k cards every 4 m bought nothing there
l.fog_all = n * 5 >= total * 4
if l.fog_all { return l.sh_buf }
if n > 0 { gpu_buffer_upload(render3d_st, l.fog_buf, n * INST_FLOATS * 4, data_of(dst), GPU_DYNAMIC) }
return l.fog_buf
}
function fog_casters_n(render3d_st: Render3dState, l: Layer) -> int {
if render3d_st.r3d_fog_wall <= 0.0 or l.fog_all { return l.n_sh }
return l.fog_n
}