From 06a2e3a504cbb00a748cedf758fd26110fea505c Mon Sep 17 00:00:00 2001 From: pkuzic Date: Fri, 25 Sep 2026 09:21:00 +0100 Subject: [PATCH] MONKEY (fog): volumetric haze uses the shared fog colour; no haze on sky pixels (Modern) Co-Authored-By: Claude Opus 5.5 --- crates/benilla-app/src/volumetric_fog.rs | 31 +++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/benilla-app/src/volumetric_fog.rs b/crates/benilla-app/src/volumetric_fog.rs index 7c59b9f2..aff59f9c 100644 --- a/crates/benilla-app/src/volumetric_fog.rs +++ b/crates/benilla-app/src/volumetric_fog.rs @@ -52,6 +52,11 @@ struct FogView { sun_strength: Vec4, // Actual celestial direction and raymarch step count. direction_steps: Vec4, + // MONKEY (fog): MonkeyFrame fog rows 1-3. Modern (fog3.x > 0) colours the haze with the shared + // fog colour function and keeps sky pixels (depth 0) out of the distance haze. + mf_fog1: Vec4, + mf_fog2: Vec4, + mf_fog3: Vec4, } impl Plugin for VolumetricFogPlugin { @@ -138,6 +143,8 @@ fn update_fog( clock: Res, weather: Res, interior: Res, + // MONKEY (fog): the Modern fog rows (Option: absent in unit tests). + monkey: Option>, mut cameras: Query<(Entity, &Camera, &mut Camera3d), With>, suns: Query<(Entity, &DirectionalLight), With>, ) { @@ -161,7 +168,12 @@ fn update_fog( let sun = lighting.celestial_dir(); let daylight = ((sun.y + 0.02) / 0.15).clamp(0.0, 1.0); let fog = Vec3::from_array(lighting.fog_color); + // MONKEY (fog): rows 1-3 as packed for the light buffer. + let rows = monkey.as_ref().map_or([[0.0; 4]; 16], |m| m.pack(0.0, 0.0)); commands.entity(entity).insert(FogView { + mf_fog1: Vec4::from_array(rows[1]), + mf_fog2: Vec4::from_array(rows[2]), + mf_fog3: Vec4::from_array(rows[3]), color_density: fog.extend( density( clock.minute_f, @@ -371,6 +383,7 @@ impl ViewNode for FogNode { const FOG_SHADER: &str = r#" #import bevy_render::view::View #import bevy_pbr::mesh_view_types::Lights +#import benilla::fog_hook @group(0) @binding(0) var scene: texture_2d; #ifdef MULTISAMPLED @group(0) @binding(1) var depth: texture_depth_multisampled_2d; @@ -381,7 +394,11 @@ const FOG_SHADER: &str = r#" @group(0) @binding(3) var lights: Lights; @group(0) @binding(4) var shadows: texture_depth_2d_array; @group(0) @binding(5) var shadow_sampler: sampler_comparison; -struct Fog { color_density: vec4, sun_strength: vec4, direction_steps: vec4 } +struct Fog { + color_density: vec4, sun_strength: vec4, direction_steps: vec4, + // MONKEY (fog): MonkeyFrame fog1..fog3. + mf_fog1: vec4, mf_fog2: vec4, mf_fog3: vec4, +} @group(0) @binding(6) var fog: Fog; // Integral of smoothstep(10, 30, distance): zero in the first ten yards, @@ -420,9 +437,17 @@ fn fragment(@builtin(position) pixel: vec4) -> @location(0) vec4 { if (distance <= 10.0) { return source; } let ray = normalize(delta); let optical_depth = fog.color_density.w * fog_path(distance); - let opacity = 1.0 - exp(-optical_depth); + var opacity = 1.0 - exp(-optical_depth); + var haze_rgb = fog.color_density.rgb; + if (fog_hook::fog_is_modern(fog.mf_fog3)) { + // MONKEY (fog): the sky (depth 0, clamped to 150 yd above) already carries its own + // horizon fog, so it takes no distance haze; the haze colour is the shared fog colour. + if (z <= 0.0) { opacity = 0.0; } + haze_rgb = fog_hook::fog_modern_colour(fog.color_density.rgb, ray, length(delta), + fog.mf_fog1, fog.mf_fog2, fog.mf_fog3); + } let luma = vec3(0.2126, 0.7152, 0.0722); - var haze = mix(source.rgb, fog.color_density.rgb, opacity); + var haze = mix(source.rgb, haze_rgb, opacity); // A darker zone colour may change hue, never the pixel's luminance. Add // neutral headroom proportionally so saturated channels cannot clip dark. let missing = max(0.0, dot(source.rgb - haze, luma));