feat(render3d): light you can see - sun shafts and valley mist

Everything before this made the air a COLOUR APPLIED TO A SURFACE. Nothing put light
in the space between surfaces, so the basin had no shafts, no pooled mist and no rays
off a ridge at any hour, whatever was done to the fog.

A half-resolution march from the camera to the depth buffer, asking the same shadow
the rest of the frame asks - the cascades, the baked height-field shadow and the cloud
mask - so a shaft is cast by the actual trees and the actual ridge and a passing cloud
dims its own rays. Henyey-Greenstein scattering, because real air throws light forward.
Density and a separate ground-hugging mist layer ride the sun's elevation, so mist
forms in the cold at either end of the day and burns off by mid-morning.

Composited with the bloom pyramid's own tent upsample under ONE/ONE - what was wanted
and already there - and before bloom, so a shaft blooms. Into post_hdr, not post_scene:
post_scene is what the water refracts and shafts added there would sit under the lake.

The tuning that mattered was the sky term, which is added at every step: at 0.06 it
accumulated into a flat grey wash lifting lit and shadowed air equally, which is the
contrast a shaft is made of, and the valley came out one pale sheet. At 0.012 the sun
dominates and there is light rather than fog. I cut the density and mist three times
before the frame looked like air instead of paint.

R3D_NOVOL=1 for an A/B; Off in Settings skips the pass whole.

400 frames at 07:00: GL 7.1 -> 7.3 s, VK 7.2 -> 7.4 s. Backends agree to 0.08/255.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-19 16:06:16 +03:00
parent d34fb5bc63
commit b12228c662
15 changed files with 252 additions and 1 deletions

View file

@ -201,6 +201,17 @@ function daylight_set(hours: int) -> void {
# zenith onto a grey day.
r3d_sky_relight = f_mul(smoothf(f_rad(fi(-10)), f_rad(fi(1)), el), f_sub(F_ONE, f_mul(fl(0.75), oc)))
# ---- what is IN the air -------------------------------------------------------------
# The volumetric march (post.ludic) needs the same story the analytic fog tells, or the
# shafts and the haze disagree. Density rides the sun's elevation like everything else; the
# MIST is separate and is the thing a valley actually does - a shallow layer that forms in
# the cold at either end of the day, lies ON the ground rather than filling the basin, and
# burns off by mid-morning. Cloud thickens the air and kills the shafts, because a shaft
# needs a disc to come from.
post_vol_density = f_mul(f_add(fl(0.00030), f_mul(fl(0.00070), lowsun)), f_sub(F_ONE, f_mul(fl(0.55), oc)))
post_vol_mist = f_mul(f_mul(fl(0.40), lowsun), f_sub(F_ONE, f_mul(fl(0.4), oc)))
post_vol_falloff = f_add(fl(0.006), f_mul(fl(0.004), lowsun))
# R3D_NOAIR=1: the air as it was before any of the above - one density, one falloff, the
# inscatter the shader used to hard-code, and no distance desaturation at all. It is here
# so a before-and-after can be shot from ONE binary at one hour, which is the only kind of
@ -215,6 +226,7 @@ function daylight_set(hours: int) -> void {
post_gain_r = fl(0.99); post_gain_g = fl(0.995); post_gain_b = F_ONE
post_contrast = fl(1.12); post_saturation = fl(1.04)
r3d_sky_relight = F_ZERO
post_vol_density = F_ZERO; post_vol_mist = F_ZERO
}
# exposure: auto-exposure must not turn the night into day
# the ceiling has to move with the moon or auto-exposure eats the difference between a