Round A: sunset family + ACES tonemap (#1-4, #6)

shader.wgsl:
  - sky_dome: zenith now tints toward a dusky-purple zenith_twi during
    twilight (was previously only the horizon picking up the warm tint).
    Top faces at sunset will read warm through the sky_vis pathway
    instead of staying cold blue. [#1]
  - apply_fog: at twilight the fog tints toward sun_tint by twi×0.45,
    so distant terrain reads warm against an orange sky instead of
    cold. [#2]
  - sky_color stars: gate flipped from (1 - day) — which still showed
    stars during dusk while the sky was bright — to a direct
    smoothstep on sun.y (-0.22..0.04). Stars now fade in only after
    civil twilight. [#3]
  - sun disc / halo: sharpness + intensity now altitude-dependent. At
    zenith it's a sharp pinpoint (sharpness 800, intensity 1.5); near
    the horizon it softens to a big atmospheric bloom (sharpness 160,
    intensity 2.2). Halo also widens and brightens at low sun. [#4]

post.wgsl:
  - ACES filmic tonemap (Narkowicz approximation) runs as the final
    step. Brightens midtones, compresses highlights smoothly into
    [0,1] instead of the previous hard clamp. Output stays linear; the
    sRGB surface handles display encoding. Foundation for everything
    HDR that follows (god rays, bloom). [#6]
This commit is contained in:
Maximus Gorog 2026-05-24 00:20:25 -06:00
parent 511798b6eb
commit 94585b1ab2
2 changed files with 50 additions and 13 deletions

View file

@ -1,6 +1,10 @@
// Step 1 of the post-process rebuild: minimal pass-through. Samples the
// offscreen scene_color and writes it straight to the surface. Effects
// (FXAA, sun shafts, tonemap) layer on top of this in later steps.
// Post pipeline. Currently:
// 1. Sample the offscreen scene_color (linear HDR-ish)
// 2. ACES tonemap to compress highlights gracefully
// 3. Output to the surface (which sRGB-encodes automatically)
//
// Effects layered on top (god rays, FXAA) will slot in between (1) and (2)
// tonemap stays last so everything benefits from the curve.
@group(0) @binding(0) var scene_color_tex: texture_2d<f32>;
@group(0) @binding(1) var scene_color_sampler: sampler;
@ -25,7 +29,21 @@ fn vs_post(@builtin(vertex_index) idx: u32) -> PostOut {
return out;
}
// Narkowicz's ACES filmic approximation. Brightens midtones, compresses
// highlights smoothly into [0, 1]. Output is linear; the sRGB surface
// encodes for display.
fn aces_tonemap(c: vec3<f32>) -> vec3<f32> {
let a = 2.51;
let b = 0.03;
let cc = 2.43;
let d = 0.59;
let e = 0.14;
return clamp((c * (a * c + b)) / (c * (cc * c + d) + e), vec3<f32>(0.0), vec3<f32>(1.0));
}
@fragment
fn fs_post(in: PostOut) -> @location(0) vec4<f32> {
return textureSample(scene_color_tex, scene_color_sampler, in.uv);
let scene = textureSample(scene_color_tex, scene_color_sampler, in.uv);
let tonemapped = aces_tonemap(scene.rgb);
return vec4<f32>(tonemapped, 1.0);
}

View file

@ -64,10 +64,15 @@ fn sky_dome(dir: vec3<f32>, sun: vec3<f32>) -> vec3<f32> {
let twi = twilight_amount(sun);
let zenith_day = vec3<f32>(0.30, 0.55, 0.88);
let zenith_night = vec3<f32>(0.02, 0.03, 0.10);
// Dusky-purple zenith during twilight in reality the entire sky
// tints warm at sunset, not just the horizon. Without this, top
// faces stay cold blue while the horizon visibly burns orange.
let zenith_twi = vec3<f32>(0.45, 0.28, 0.40);
let horizon_day = vec3<f32>(0.82, 0.92, 0.99);
let horizon_twi = vec3<f32>(1.00, 0.55, 0.28);
let horizon_night = vec3<f32>(0.03, 0.04, 0.10);
let zenith = mix(zenith_night, zenith_day, day);
let zenith_base = mix(zenith_night, zenith_day, day);
let zenith = mix(zenith_base, zenith_twi, twi * 0.65);
let horizon = mix(mix(horizon_night, horizon_day, day), horizon_twi, twi);
let up = clamp(dir.y, -1.0, 1.0);
let gradient_t = pow(max(up, 0.0), 0.55);
@ -127,11 +132,15 @@ fn sky_color(dir: vec3<f32>) -> vec3<f32> {
let below = step(up, 0.0) * 0.2;
sky = sky * (1.0 - below);
// Stars: fade in at night with slight twinkle.
if (night > 0.05) {
// Stars: only visible once the sun is well below the horizon. The
// old `1 - day` gate showed stars during twilight (when day < 1 but
// the sky was still bright). The new gate is tied to sun altitude
// directly so stars fade in *after* civil twilight, not during it.
let star_fade = 1.0 - smoothstep(-0.22, 0.04, sun.y);
if (star_fade > 0.05) {
let st = star_field(dir);
let twinkle = 0.7 + 0.3 * sin(t * 6.0 + dir.x * 100.0 + dir.z * 130.0);
sky = sky + vec3<f32>(st * night * twinkle);
sky = sky + vec3<f32>(st * star_fade * twinkle);
}
// Cloud layer fbm scrolled across an imaginary plane high above.
@ -146,11 +155,16 @@ fn sky_color(dir: vec3<f32>) -> vec3<f32> {
sky = mix(sky, cloud_col, mask * (0.55 + 0.25 * day));
}
// Sun disc + halo.
// Sun disc + halo. The disc softens and spreads as the sun nears
// the horizon atmospheric scattering blooms the apparent disc at
// low angles. Sharp pin-point at zenith, big soft circle at dusk.
let sun_col = sun_tint(sun);
let cos_s = max(dot(dir, sun), 0.0);
let disc = pow(cos_s, 800.0) * 1.5 * smoothstep(-0.05, 0.05, sun.y);
let halo = pow(cos_s, 5.0) * 0.20 * day;
let alt = clamp(sun.y, 0.0, 1.0);
let disc_sharpness = mix(160.0, 800.0, alt);
let disc_intensity = mix(2.2, 1.5, alt);
let disc = pow(cos_s, disc_sharpness) * disc_intensity * smoothstep(-0.05, 0.05, sun.y);
let halo = pow(cos_s, mix(3.0, 5.0, alt)) * mix(0.35, 0.20, alt) * day;
sky = sky + sun_col * (disc + halo);
// Moon disc opposite the sun, faint white, night only.
@ -234,14 +248,19 @@ fn fog_factor(dist: f32) -> f32 {
/// Blend `lit` toward sky color along the view ray when the fragment
/// is far enough to be fogged. Defers the (expensive) full `sky_color`
/// call until the factor is actually nonzero.
/// call until the factor is actually nonzero. At twilight the fog
/// further biases toward warm sun-tint so distant terrain reads
/// orange/pink against an orange sky instead of cold against orange.
fn apply_fog(lit: vec3<f32>, dist: f32, view_dir: vec3<f32>) -> vec3<f32> {
let t = fog_factor(dist);
if (t <= 0.001) {
return lit;
}
let sun = sun_direction(scene_time());
let twi = twilight_amount(sun);
let sky = sky_color(-view_dir);
return mix(lit, sky, t);
let fog_col = mix(sky, sky * sun_tint(sun), twi * 0.45);
return mix(lit, fog_col, t);
}
// ---------------- 5a. Terrain pipeline ----------------