MONKEY (fix-post): bloom keeps the base image; window gain on the light term only

Review P4: emissive_boost now lifts only above a 0.6 knee (equal to the old
gain at 1.0), so additive spells and lava keep their look and the excess feeds
the bloom. Review P3: the window gain applies to static_gx's SIDN light term
before fog, not to the whole fogged batch. Review P6: Low-tier extract taps
scale with the quarter-size target. Review P7: excess capped at 64 (no NaN).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pkuzic
2026-09-25 16:55:55 +01:00
co-authored by Claude Opus 5.5
parent 5854ac39c6
commit 75eb764df4
3 changed files with 36 additions and 21 deletions
+17 -13
View File
@@ -21,20 +21,24 @@ fn tap(tex: texture_2d<f32>, uv: vec2<f32>, offset: vec2<f32>) -> vec4<f32> {
@fragment
fn fs_extract(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
// 13-tap downfilter: a broad, stable footprint when Low uses a quarter-size target.
// MONKEY (fix-post): the footprint scales with the target divisor (Low = quarter size, so
// 2x offsets), else 16 source pixels fed each output from a 5x5 area and thin sparks shimmered.
let spread = select(1.0, 2.0, bloom.tier < 1.5);
var c = tap(source_tex, in.uv, vec2(0.0)) * 0.20;
c += tap(source_tex, in.uv, vec2( 1.0, 0.0)) * 0.10;
c += tap(source_tex, in.uv, vec2(-1.0, 0.0)) * 0.10;
c += tap(source_tex, in.uv, vec2( 0.0, 1.0)) * 0.10;
c += tap(source_tex, in.uv, vec2( 0.0, -1.0)) * 0.10;
c += tap(source_tex, in.uv, vec2( 1.0, 1.0)) * 0.06;
c += tap(source_tex, in.uv, vec2(-1.0, 1.0)) * 0.06;
c += tap(source_tex, in.uv, vec2( 1.0, -1.0)) * 0.06;
c += tap(source_tex, in.uv, vec2(-1.0, -1.0)) * 0.06;
c += tap(source_tex, in.uv, vec2( 2.0, 0.0)) * 0.04;
c += tap(source_tex, in.uv, vec2(-2.0, 0.0)) * 0.04;
c += tap(source_tex, in.uv, vec2( 0.0, 2.0)) * 0.04;
c += tap(source_tex, in.uv, vec2( 0.0, -2.0)) * 0.04;
let excess = max(c.rgb - vec3(1.0), vec3(0.0));
c += tap(source_tex, in.uv, vec2( 1.0, 0.0) * spread) * 0.10;
c += tap(source_tex, in.uv, vec2(-1.0, 0.0) * spread) * 0.10;
c += tap(source_tex, in.uv, vec2( 0.0, 1.0) * spread) * 0.10;
c += tap(source_tex, in.uv, vec2( 0.0, -1.0) * spread) * 0.10;
c += tap(source_tex, in.uv, vec2( 1.0, 1.0) * spread) * 0.06;
c += tap(source_tex, in.uv, vec2(-1.0, 1.0) * spread) * 0.06;
c += tap(source_tex, in.uv, vec2( 1.0, -1.0) * spread) * 0.06;
c += tap(source_tex, in.uv, vec2(-1.0, -1.0) * spread) * 0.06;
c += tap(source_tex, in.uv, vec2( 2.0, 0.0) * spread) * 0.04;
c += tap(source_tex, in.uv, vec2(-2.0, 0.0) * spread) * 0.04;
c += tap(source_tex, in.uv, vec2( 0.0, 2.0) * spread) * 0.04;
c += tap(source_tex, in.uv, vec2( 0.0, -2.0) * spread) * 0.04;
// MONKEY (fix-post): cap the excess so a +inf pixel cannot become an inf/inf NaN halo.
let excess = min(max(c.rgb - vec3(1.0), vec3(0.0)), vec3(64.0));
let knee = excess * excess / (excess + vec3(0.50));
// Frost-Nova/firework stacks can reach 5-10x; compress them without hard clipping.
let capped = vec3(5.0) * (vec3(1.0) - exp(-knee / vec3(5.0)));
@@ -26,7 +26,16 @@ fn emissive_boost(
var gain = mix(1.45, 1.85, high);
if (kind == EMISSIVE_WMO_WINDOW) { gain = mix(1.70, 2.35, high); }
if (kind == EMISSIVE_MAGMA) { gain = mix(1.80, 2.60, high); }
return color * mix(1.0, gain, clamp(authored_weight, 0.0, 1.0));
// MONKEY (fix-post): boost only above a knee, so the base image keeps its look (below 0.6
// untouched) and the excess goes to the bloom extract; at 1.0 the result equals the old gain.
// A flat multiply saturated every additive spell and the lava to white once the combine clamps.
// A window's gain is applied to its emissive light term alone (static_gx), so it stays flat.
if (kind == EMISSIVE_WMO_WINDOW) {
return color * mix(1.0, gain, clamp(authored_weight, 0.0, 1.0));
}
let knee = 0.6;
let over = max(color - vec3<f32>(knee), vec3<f32>(0.0));
return color + over * ((gain - 1.0) * clamp(authored_weight, 0.0, 1.0) / (1.0 - knee));
}
// Distant magma keeps part of its emissive body instead of converging completely to fog.
@@ -1534,7 +1534,13 @@ fn fragment(in: GxVsOut) -> @location(0) vec4<f32> {
sidn_w = 0.0;
}
}
let sidn_e = sidn_rgb * (wow_light.grade.x * sidn_w);
var sidn_e = sidn_rgb * (wow_light.grade.x * sidn_w);
// MONKEY (fix-post): the bloom window gain lifts only this light term, before fog (it used
// to multiply the whole fogged batch, so fogged buildings glowed as fog x 2.35 at night).
if ((in.word & WORD_WINDOW) != 0u) {
sidn_e = emissive_hook::emissive_boost(
sidn_e, emissive_hook::EMISSIVE_WMO_WINDOW, wow_light.light_diffuse.w, 1.0);
}
// MONKEY (ext-class night law): plenty of geometry INSIDE a building is authored
// EXTERIOR-class — the Goldshire inn's whole 57.7 yd shell is one group (`upstairs`,
// MOGP 0x0a09, and it holds the stair down to the cellar), its east stairwell annex is
@@ -2009,12 +2015,8 @@ fn fragment(in: GxVsOut) -> @location(0) vec4<f32> {
rgb = fog_hook::apply_fog(rgb, fog_color.xyz, fog_span, eye_z, in.world_position.xyz,
view.world_position, true, wow_light.monkey);
}
// MONKEY (post): SIDN/window batches cross 1.0 only at night and when bloom is armed.
if ((in.word & WORD_WINDOW) != 0u) {
rgb = emissive_hook::emissive_boost(
rgb, emissive_hook::EMISSIVE_WMO_WINDOW, wow_light.light_diffuse.w,
wow_light.grade.x);
}
// MONKEY (post): SIDN/window light crosses 1.0 only at night and when bloom is armed; the
// gain is applied to `sidn_e` above (MONKEY fix-post).
// Gamma-space output; alpha pinned 1.0, every draw here is opaque.
return vec4<f32>(rgb, 1.0);
}