diff --git a/crates/benilla-app/src/post/bloom.wgsl b/crates/benilla-app/src/post/bloom.wgsl index 7313a1d4..98cc1681 100644 --- a/crates/benilla-app/src/post/bloom.wgsl +++ b/crates/benilla-app/src/post/bloom.wgsl @@ -21,20 +21,24 @@ fn tap(tex: texture_2d, uv: vec2, offset: vec2) -> vec4 { @fragment fn fs_extract(in: FullscreenVertexOutput) -> @location(0) vec4 { // 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))); diff --git a/crates/benilla-assets/src/shaders/emissive_hook.wgsl b/crates/benilla-assets/src/shaders/emissive_hook.wgsl index 8668b08d..90c3c720 100644 --- a/crates/benilla-assets/src/shaders/emissive_hook.wgsl +++ b/crates/benilla-assets/src/shaders/emissive_hook.wgsl @@ -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(knee), vec3(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. diff --git a/crates/benilla-world/src/shaders/static_gx.wgsl b/crates/benilla-world/src/shaders/static_gx.wgsl index dba4e6bb..ad217a91 100644 --- a/crates/benilla-world/src/shaders/static_gx.wgsl +++ b/crates/benilla-world/src/shaders/static_gx.wgsl @@ -1534,7 +1534,13 @@ fn fragment(in: GxVsOut) -> @location(0) vec4 { 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 { 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(rgb, 1.0); }