From 4d485a17783f8e6c0618487c83ed4524c162b917 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 16:10:58 +0300 Subject: [PATCH] render3d: the shadow array holds only the cascades a fog wall needs, and a thick fog takes the whole sky A cascade that begins past the wall was fitted and cleared every frame for nothing: the array is now made with the ones that begin inside it (2 at 30 m, 3 at 175 m, 5 without a fog) and made again when the wall changes that count; shadow maps 80 -> 32 MB at 30 m, 48 MB at 175 m. A layer the array lacks clamps to its last, read only past the wall where everything is fogged. The sky: at walls of 110 m and nearer the whole sky is the fog's colour, overhead too, with a soft glow at the sun or moon and no stars or clouds through it, easing out by 175 m; from 175 m on the horizon band alone, and off as before. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.render3d/env.ludic | 1 + packages/ludic.render3d/gpu_vk.ludic | 2 +- packages/ludic.render3d/render.ludic | 1 + packages/ludic.render3d/shaders/sky.frag | 9 ++++++ .../shaders/spv/428caa3a.frag.spv | Bin 20100 -> 21112 bytes packages/ludic.render3d/shadow.ludic | 30 ++++++++++++++++-- 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index 5900e486..3a879c4d 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -199,6 +199,7 @@ export state Render3dState { gvk_al_tag: []int = null # per allocation id: owner * 4 + image * 2 + host (vkmem_report.ludic) gvk_tag: int = 0 # the owner allocations are made for now (VKM_*) vkmem_at: int = 0 # R3D_VKMEM=: the report is printed at that frame + sh_layers: int = 5 # the shadow array's layers: the cascades the fog wall needs (shadow_fog) fog_imp_released: int = 0 # impostor atlases let go under a fog wall (fog_impostors.ludic) fog_imp_rebaked: int = 0 fog_imp_rebake_us: int = 0 diff --git a/packages/ludic.render3d/gpu_vk.ludic b/packages/ludic.render3d/gpu_vk.ludic index 130c456f..ac0008e1 100644 --- a/packages/ludic.render3d/gpu_vk.ludic +++ b/packages/ludic.render3d/gpu_vk.ludic @@ -431,7 +431,7 @@ function gvk_mem_new(render3d_st: mut Render3dState, req: bytes, want: int, imag let span = (size + align - 1) / align * align # an impostor atlas has memory of its own: a fog wall lets it go (fog_impostors.ludic), and inside a # shared block its room went back to nobody while the block's other tenants stayed - if size <= gvk_own_over(render3d_st) and render3d_st.gvk_tag != VKM_IMPOSTOR { + if size <= gvk_own_over(render3d_st) and render3d_st.gvk_tag != VKM_IMPOSTOR and render3d_st.gvk_tag != VKM_SHADOW { var kind = t * 2 if image { kind += 1 } var i = 0 diff --git a/packages/ludic.render3d/render.ludic b/packages/ludic.render3d/render.ludic index cca08c88..f2cf20b4 100644 --- a/packages/ludic.render3d/render.ludic +++ b/packages/ludic.render3d/render.ludic @@ -60,6 +60,7 @@ export function r3d_fog_wall(render3d_st: mut Render3dState, dist: float) -> voi render3d_st.sc_view_gen += 1 cam_update(render3d_st) fog_impostors(render3d_st) + shadow_fog(render3d_st) } # how far anything is drawn: the fog wall and a margin, or `far` (0: no limit of its own) without one function r3d_reach(render3d_st: Render3dState, far: float) -> float { diff --git a/packages/ludic.render3d/shaders/sky.frag b/packages/ludic.render3d/shaders/sky.frag index db0bbd44..e2cf74a1 100644 --- a/packages/ludic.render3d/shaders/sky.frag +++ b/packages/ludic.render3d/shaders/sky.frag @@ -117,6 +117,15 @@ void main() { if (u_fog_wall > 0.0) { float band = clamp(90.0 / u_fog_wall, 0.06, 0.75); col = mix(col, fogWallCol(dir), 1.0 - smoothstep(-0.02, band, dir.y)); + // a thick fog (110 m and nearer, easing out by 175 m) takes the whole sky, overhead too: no + // stars and no clouds through it, only a soft glow where the sun (in fogWallCol) or the moon is + float cover = 1.0 - smoothstep(110.0, 175.0, u_fog_wall); + if (cover > 0.0) { + vec3 thick = fogWallCol(dir); + float mg = pow(max(dot(dir, u_moon_dir), 0.0), 12.0) * u_moon_illum * night * step(-0.08, u_moon_dir.y); + thick += vec3(0.55, 0.6, 0.7) * 0.05 * mg; + col = mix(col, thick, cover); + } } o_color = vec4(sane(col), 1.0); } diff --git a/packages/ludic.render3d/shaders/spv/428caa3a.frag.spv b/packages/ludic.render3d/shaders/spv/428caa3a.frag.spv index 9e30bcf3cb57d847103db1a9eb1ab6efef251195..ed325e79fb3a3bb0f7347067a022ae9ec05ca5f2 100644 GIT binary patch delta 1660 zcmY+E$xl;J6vj^}bE%++$|!>alZ;NxlxkR*s2E)sF{Bg(qEJ&&moG*QJBh)qD_xtI zE==5*nD}296PG4#To}QX-|ulRsV_YGeczexIrp^BUPrdyM&i+ewi2_5#jMmWyo_Ip z47|U2r93E%nx!+37PDqnWQA5KzP`MYzUz3E`2AEiwHorMRg14K?fMP~XXetGdzow) zJNTb(v5ks9oLk9eQwuApwe+G=gnnb8KA&Bgyt-y8l-C!t50+C4na80e znX8R$6b4jkB3#e3PD$n#VSViJR*ZVEQvrKH9?!QYoW01|;rVusLcAtMzE(rXcSt&i zQkNi^OH^Hn1zoCLEU8Flv#(R07l*tz>3MC)Clj6@6Mr}Tebn>g;+vm;@9`7j9kZ2V z-OA3HPKpzQAGT)I?T^}gIFMU%7}*t!!RgO@;$e~5&4P(;O*f98__v4|Ix&ly7{lf9 zoMH0RTA7V+?bNp=CZvq(rR=1$q7eaSoIE}Q)63_{-0g;q$i*6I>>nNB$}ri#e9r!P zjz9b7IsSvWAB`gwK|=j3iI&LBp8ZrGV@kR^M4n9wn1ADpf zHRm_3>2X9fpD!ki_EG*l@~eniV2Hyae-yA!nCGt!Afv_gqHSv*!6c%G{lYhNP>Rqe zd`L;L9T0BTCpvcKg63ln3KL7?A5`CJB+&VcTJuhaq@llZogEe?5WPZW==O*((`eU= zohH1k&e`gmA8YF;T`x?%HR=U!)D03bJa+{fg-L?$$JZqMMkn_^N5%0&Pv}ToW3#Yt z=CZhtZIO=6AFx%J!v}r(cU=evpSVq!;-hoFz=R+dpsph0$;R8nB}>uPOw$%8LN$&YY6nvXxFCUlF#F!I7gC?b7FF_@hg+)}b5S4KG5CiJTiC9S*A*D!aJ6j96 z%F17&|G_SH&RjNfk6Yf(%-i>N_QtR3@kKewL@h0%#FmUKJUe?zzaKYqXF4h3w>ti% zABdKeSim=4;9ZFTTQUoL*Xjp>*X?`PogV0@t`PCm}QbbVa0P2Ytj_VvqT^pS^u9uAt)9hCj67d4p6l6>T zH+(2G?fwd8ry${z6Rf(yyg#`$U<=s+IDJa&tm6g_@C@*`n(#c+!^P>5F~#)6Sd^?3 zM{i5D#vGU;7N?esN{%Op>54J!KofE7hQ;Nf()3`3UzK`(@QWAw!R)cgTj<0$NaJK1 z{5D+hClhbt?$k2a+iZb+0FM5$*pp{Wxt!r(^PjT~ht{$$>1y<7d5~ void { render3d_st.sh_split[0] = 16.0; render3d_st.sh_split[1] = 60.0; render3d_st.sh_split[2] = 250.0; render3d_st.sh_split[3] = 1100.0; render3d_st.sh_split[4] = 6000.0 render3d_st.sh_tmp_proj = m4_new(); render3d_st.sh_tmp_vp = m4_new(); render3d_st.sh_tmp_inv = m4_new(); render3d_st.sh_tmp_view = m4_new() render3d_st.sh_corner = floats(3) + shadow_fog(render3d_st) } # A shadow resolution setting: 1024, 2048 or 4096 per cascade. The depth layers are made again at @@ -64,7 +65,8 @@ function shadow_set_res(render3d_st: mut Render3dState, r: int) -> void { function shadow_make_tex(render3d_st: mut Render3dState) -> void { render3d_st.sh_tex = gpu_tex_new(render3d_st) gpu_tex_bind(render3d_st, GPU_TEX2D_ARRAY, render3d_st.sh_tex) - gpu_tex_image3d(render3d_st, GL_DEPTH_COMPONENT32F, render3d_st.shadow_res, render3d_st.shadow_res, SHADOW_CASCADES, GL_DEPTH_COMPONENT, GL_FLOAT, null) + render3d_st.sh_layers = shadow_layers_for(render3d_st) + gpu_tex_image3d(render3d_st, GL_DEPTH_COMPONENT32F, render3d_st.shadow_res, render3d_st.shadow_res, render3d_st.sh_layers, GL_DEPTH_COMPONENT, GL_FLOAT, null) gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR) gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR) gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) @@ -159,6 +161,8 @@ function shadow_pass(render3d_st: mut Render3dState) -> void { prof_cpu_mark(render3d_st, "shadow fit") let past = wall > 0.0 and near >= wall near = render3d_st.sh_split[c] + # a cascade the fog's shadow array has no layer for: fitted (its uniforms stay sane), never drawn + if c >= render3d_st.sh_layers { continue } gpu_fb_depth_layer(render3d_st, render3d_st.sh_tex, c) if render3d_st.r3d_debug and c == 0 { shadow_debug_fbo(render3d_st) } gpu_clear(render3d_st, GL_DEPTH_BUFFER_BIT) @@ -179,7 +183,7 @@ function shadow_pass(render3d_st: mut Render3dState) -> void { @alloc_ok("asked for by the player or a tool, not by the frame") function shadow_dump(render3d_st: mut Render3dState) -> void { let n = render3d_st.shadow_res * render3d_st.shadow_res - let buf = floats(n * SHADOW_CASCADES) + let buf = floats(n * render3d_st.sh_layers) gpu_tex_bind(render3d_st, GPU_TEX2D_ARRAY, render3d_st.sh_tex) gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE) gpu_tex_read(render3d_st, GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, data_of(buf)) @@ -300,3 +304,25 @@ function shadow_debug_range(render3d_st: mut Render3dState, prog: int) -> void { # messages, each built in a function of its own so the path that says it holds no allocation @alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch") function shadow_say_no_memory(r: int, was: int) -> void { print(`r3d: shadows: no memory for {r} x {r} cascades; keeping {was}`) } + +# ---- the fog wall ----------------------------------------------------------------------------- +# A cascade that begins past the wall is never drawn, so the array holds only the ones that begin +# inside it (16 MB each at 2048): two at 30 m, three at 175 m, all five without a fog. A layer the +# array lacks clamps to its last, and what reads it lies past the wall, fully fogged. +function shadow_layers_for(render3d_st: Render3dState) -> int { + let wall = r3d_reach(render3d_st, 0.0) + if wall <= 0.0 or render3d_st.sh_split == null { return SHADOW_CASCADES } + var n = 1 + while n < SHADOW_CASCADES and render3d_st.sh_split[n - 1] < wall { n += 1 } + return n +} + +# the array made again when the wall changes how many cascades it needs +function shadow_fog(render3d_st: mut Render3dState) -> void { + if render3d_st.sh_tex == 0 or shadow_layers_for(render3d_st) == render3d_st.sh_layers { return } + let was = render3d_st.gvk_tag + render3d_st.gvk_tag = VKM_SHADOW + gpu_tex_free(render3d_st, render3d_st.sh_tex) + shadow_make_tex(render3d_st) + render3d_st.gvk_tag = was +}