MONKEY (p0): fog_hook.wgsl, one distance-fog law for every receiver
`benilla::fog_hook` (fog_linear / fog_sample / apply_fog) replaces the seven hand-rolled copies of the linear planar-eye-Z fog: terrain, wow_model, static_gx, liquid, enhanced_water (factor form), wow_effect (scene fog; the rain streak fade uses fog_linear) and wdl (the hull's own 0..1 yd pair). Pure functions, values passed as arguments, same expressions in the same order, so the classic result is bit-identical. `fog_sample` branches on MonkeyFrame.fog_model (fog3.x) as the FOG lane's extension point, currently returning the classic result. wow_effect gains a world_pos varying for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3415538908
commit
fc99f2c858
@@ -35,6 +35,10 @@ is its own module, documented in `WATER.md`.
|
||||
## Where the code lives
|
||||
|
||||
- **Shaders**: `crates/benilla-assets/src/shaders/shadow_hook.wgsl` (shared shadow sampling),
|
||||
`fog_hook.wgsl` (MONKEY p0: the ONE distance-fog law — `fog_linear`, `fog_sample`, `apply_fog`;
|
||||
terrain, wow_model, static_gx, liquid, enhanced_water, wow_effect and wdl all call it; classic is
|
||||
bit-identical to the old per-shader copies, and `MonkeyFrame.fog_model` = 1 is the FOG lane's
|
||||
extension point), `monkey_frame.wgsl` (the MonkeyFrame struct),
|
||||
`crates/benilla-world/src/shaders/torch_depth.wgsl`, and the lighting lanes inside
|
||||
`static_gx.wgsl`, `wow_model.wgsl` and `terrain.wgsl`. The three receivers mirror each other; the
|
||||
comments say where.
|
||||
|
||||
@@ -32,6 +32,8 @@ pub fn register_shaders(app: &mut App) {
|
||||
// MONKEY (p0 MonkeyFrame): `benilla::monkey_frame`, the struct every `WowLight` mirror declares
|
||||
// after its point table (LIGHTING.md "Invariants").
|
||||
load_shader_library!(app, "shaders/monkey_frame.wgsl");
|
||||
// MONKEY (p0 fog hook): `benilla::fog_hook`, the one distance-fog law all seven receivers call.
|
||||
load_shader_library!(app, "shaders/fog_hook.wgsl");
|
||||
// MONKEY (enhanced water): the optional water module, `benilla::enhanced_water` (WATER.md).
|
||||
load_shader_library!(app, "shaders/enhanced_water.wgsl");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#import bevy_pbr::mesh_view_bindings::{view, globals}
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(103) var scene_depth: texture_2d<f32>;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(106) var scene_colour: texture_2d<f32>;
|
||||
@@ -86,8 +88,9 @@ fn water_fog(world_pos: vec3<f32>, room_fog: u32) -> vec4<f32> {
|
||||
}
|
||||
if colour.w <= 0.5 { return vec4<f32>(colour.rgb, 1.0); }
|
||||
let eye_z = -(view.view_from_world * vec4<f32>(world_pos, 1.0)).z;
|
||||
let factor = clamp((span.y - eye_z) / max(span.y - span.x, 0.001), 0.0, 1.0);
|
||||
return vec4<f32>(colour.rgb, factor);
|
||||
// MONKEY (p0 fog hook): the shared fog law (fog_hook.wgsl), as a factor; classic is bit-identical.
|
||||
return fog_hook::fog_sample(colour.rgb, span, eye_z, world_pos, view.world_position, true,
|
||||
water_light.monkey);
|
||||
}
|
||||
|
||||
// True where this surface takes the Enhanced path: water (not magma/slime), a tier above
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#define_import_path benilla::fog_hook
|
||||
|
||||
// MONKEY (p0 fog hook): the ONE place the distance fog is computed.
|
||||
//
|
||||
// Every receiver — terrain, wow_model, static_gx, liquid, enhanced_water, wow_effect, wdl — calls
|
||||
// in here instead of hand-rolling the fog law, so a new fog model touches this file only (the
|
||||
// `shadow_hook.wgsl` pattern). Pure functions: the receivers bind the shared light buffer at
|
||||
// different slots (material binding 90, group 2 binding 0, group 1 binding 2, binding 105), so
|
||||
// every value arrives as an argument and this file owns no bindings.
|
||||
//
|
||||
// The classic law is the reference's GL_LINEAR fog on PLANAR eye-Z in gamma space (terrain.wgsl
|
||||
// has the full note): `factor = clamp((end - eye_z) / max(end - start, 0.001), 0, 1)`, then
|
||||
// `mix(fog_rgb, rgb, factor)`. The expressions below are the ones the receivers carried, in the
|
||||
// same order, so the classic result is bit-identical.
|
||||
//
|
||||
// Extension point for the FOG lane: `fog_sample` branches on `mf.fog3.x` (`MonkeyFrame.fog_model`,
|
||||
// 0 classic / 1 modern). The modern arm currently returns the classic result.
|
||||
|
||||
#import benilla::monkey_frame::MonkeyFrame
|
||||
|
||||
// The classic survival factor: 1 = no fog, 0 = fully fogged.
|
||||
fn fog_linear(eye_z: f32, span: vec2<f32>) -> f32 {
|
||||
let denom = max(span.y - span.x, 0.001);
|
||||
return clamp((span.y - eye_z) / denom, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// The fog a surface stands in: rgb = the fog colour, w = how much of the surface survives (1 = no
|
||||
// fog). `fog_rgb` is the colour the caller's policy picked; `scene_colour` is true when that is the
|
||||
// scene's own fog colour (false for the forced black/white/grey policies of additive, Mod and Mod2x
|
||||
// batches, which a new model may attenuate but must not recolour). `span` = (start, end) yd,
|
||||
// `eye_z` = planar eye depth (yd), `world_pos` / `camera_pos` in Bevy world space.
|
||||
fn fog_sample(
|
||||
fog_rgb: vec3<f32>,
|
||||
span: vec2<f32>,
|
||||
eye_z: f32,
|
||||
world_pos: vec3<f32>,
|
||||
camera_pos: vec3<f32>,
|
||||
scene_colour: bool,
|
||||
mf: MonkeyFrame,
|
||||
) -> vec4<f32> {
|
||||
let classic = vec4<f32>(fog_rgb, fog_linear(eye_z, span));
|
||||
if (mf.fog3.x > 0.5) {
|
||||
// FOG lane: the modern model (height / sun / end-colour / curve fog) goes here, reading
|
||||
// mf.fog0..fog3. Until it lands, the modern arm is the classic law.
|
||||
return classic;
|
||||
}
|
||||
return classic;
|
||||
}
|
||||
|
||||
// Fog `rgb` in place: `mix(fog colour, rgb, survival)`. Same arguments as `fog_sample`.
|
||||
fn apply_fog(
|
||||
rgb: vec3<f32>,
|
||||
fog_rgb: vec3<f32>,
|
||||
span: vec2<f32>,
|
||||
eye_z: f32,
|
||||
world_pos: vec3<f32>,
|
||||
camera_pos: vec3<f32>,
|
||||
scene_colour: bool,
|
||||
mf: MonkeyFrame,
|
||||
) -> vec3<f32> {
|
||||
let f = fog_sample(fog_rgb, span, eye_z, world_pos, camera_pos, scene_colour, mf);
|
||||
return mix(f.rgb, rgb, f.w);
|
||||
}
|
||||
@@ -32,6 +32,8 @@
|
||||
#import benilla::enhanced_water::{water_active, water_swell, enhanced_water, WaterFragment}
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(100) var frames: texture_2d_array<f32>;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var frames_samp: sampler;
|
||||
@@ -132,9 +134,9 @@ fn apply_fog(rgb: vec3<f32>, world_pos: vec3<f32>, room_fog: u32) -> vec3<f32> {
|
||||
return rgb;
|
||||
}
|
||||
let eye_z = -(view.view_from_world * vec4<f32>(world_pos, 1.0)).z;
|
||||
let denom = max(fog_span.y - fog_span.x, 0.001);
|
||||
let factor = clamp((fog_span.y - eye_z) / denom, 0.0, 1.0);
|
||||
return mix(fog_color.xyz, rgb, factor);
|
||||
// MONKEY (p0 fog hook): the shared fog law (fog_hook.wgsl); classic is bit-identical.
|
||||
return fog_hook::apply_fog(rgb, fog_color.xyz, fog_span, eye_z, world_pos, view.world_position,
|
||||
true, wow_light.monkey);
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#import benilla::shadow_hook
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(100) var layer_array: texture_2d_array<f32>;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(104) var alpha_array: texture_2d_array<f32>;
|
||||
@@ -780,9 +782,9 @@ fn fragment(in: TerrainVsOut) -> @location(0) vec4<f32> {
|
||||
// the per-pixel form is what survives Bevy's mesh interpolators without a custom slot.
|
||||
if (wow_light.fog_color.w > 0.5) {
|
||||
let eye_z = -(view.view_from_world * vec4<f32>(in.world_position.xyz, 1.0)).z;
|
||||
let denom = max(wow_light.fog_params.y - wow_light.fog_params.x, 0.001);
|
||||
let factor = clamp((wow_light.fog_params.y - eye_z) / denom, 0.0, 1.0);
|
||||
tuned = mix(wow_light.fog_color.xyz, tuned, factor);
|
||||
// MONKEY (p0 fog hook): the shared fog law (fog_hook.wgsl); classic is bit-identical.
|
||||
tuned = fog_hook::apply_fog(tuned, wow_light.fog_color.xyz, wow_light.fog_params.xy, eye_z,
|
||||
in.world_position.xyz, view.world_position, true, wow_light.monkey);
|
||||
}
|
||||
|
||||
// Raw gamma out: the framebuffer holds gamma bytes and blends in gamma like the reference's;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
/// How far the band reaches inside the far-clip wall: the reference's far-band near plane
|
||||
/// `farclip - 33.0` (`[0x8101b0]`, about one WDL outer cell).
|
||||
@@ -81,6 +83,10 @@ fn fragment(in: WdlVsOut) -> WdlFsOut {
|
||||
var rgb = vec3<f32>(1.0);
|
||||
if (w.fog_color.w > 0.5) {
|
||||
rgb = w.fog_color.xyz;
|
||||
// MONKEY (p0 fog hook): the hull's own pair (start 0, end 1 yd) through the shared law, so
|
||||
// a new fog model colours the horizon the way it colours the world; classic = the line above.
|
||||
rgb = fog_hook::apply_fog(vec3<f32>(1.0), rgb, vec2<f32>(0.0, 1.0), eye_z,
|
||||
in.world_position.xyz, view.world_position, true, w.monkey);
|
||||
}
|
||||
|
||||
var out: WdlFsOut;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#import benilla::shadow_hook
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
// bevy_pbr 0.18.1's `forward_io::FragmentOutput`. No depth output: a fragment depth write costs
|
||||
// the pipeline early-Z, so the sky lane pins its depth in the vertex stage.
|
||||
@@ -1720,13 +1722,13 @@ fn fragment(in: WowVsOut, @builtin(front_facing) is_front: bool) -> WowFragOut {
|
||||
let fog_policy = (u32(m.clutter_fade.z) >> 4u) & 7u;
|
||||
if (fog_color.w > 0.5 && fog_policy != 4u) {
|
||||
let eye_z = -(view.view_from_world * vec4<f32>(in.world_position.xyz, 1.0)).z;
|
||||
let denom = max(fog_span.y - fog_span.x, 0.001);
|
||||
let factor = clamp((fog_span.y - eye_z) / denom, 0.0, 1.0);
|
||||
var fog_rgb = fog_color.xyz;
|
||||
if (fog_policy == 1u) { fog_rgb = vec3<f32>(0.0); }
|
||||
else if (fog_policy == 2u) { fog_rgb = vec3<f32>(1.0); }
|
||||
else if (fog_policy == 3u) { fog_rgb = vec3<f32>(0.50196078); }
|
||||
rgb = mix(fog_rgb, rgb, factor);
|
||||
// MONKEY (p0 fog hook): the shared fog law (fog_hook.wgsl); classic is bit-identical.
|
||||
rgb = fog_hook::apply_fog(rgb, fog_rgb, fog_span, eye_z, in.world_position.xyz,
|
||||
view.world_position, fog_policy == 0u, wow_light.monkey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#import benilla::shadow_hook
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
// Group 0 is Bevy's standard mesh-view bind group (view matrices, directional-light records and
|
||||
// the shadow textures the retained pass reads).
|
||||
@@ -1980,9 +1982,9 @@ fn fragment(in: GxVsOut) -> @location(0) vec4<f32> {
|
||||
fog_span = wow_light.wmo_fog_params.xy;
|
||||
}
|
||||
if (fog_color.w > 0.5 && (in.word & WORD_FOG_OFF) == 0u) {
|
||||
let denom = max(fog_span.y - fog_span.x, 0.001);
|
||||
let factor = clamp((fog_span.y - eye_z) / denom, 0.0, 1.0);
|
||||
rgb = mix(fog_color.xyz, rgb, factor);
|
||||
// MONKEY (p0 fog hook): the shared fog law (fog_hook.wgsl); classic is bit-identical.
|
||||
rgb = fog_hook::apply_fog(rgb, fog_color.xyz, fog_span, eye_z, in.world_position.xyz,
|
||||
view.world_position, true, wow_light.monkey);
|
||||
}
|
||||
// Gamma-space output; alpha pinned 1.0, every draw here is opaque.
|
||||
return vec4<f32>(rgb, 1.0);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#import bevy_render::view::View
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
// MONKEY (p0 fog hook): the one distance-fog law every receiver calls.
|
||||
#import benilla::fog_hook
|
||||
|
||||
// Prefix of `lighting::global_light`'s buffer; keep in sync with wow_model.wgsl's copy.
|
||||
struct WowLight {
|
||||
@@ -73,6 +75,8 @@ struct VertexOutput {
|
||||
@location(1) color: vec4<f32>,
|
||||
// Planar eye-Z for fog and the farclip wall, positive in front of the camera.
|
||||
@location(2) view_z: f32,
|
||||
// MONKEY (p0 fog hook): world position (Bevy space) for the fog hook's modern arm.
|
||||
@location(3) world_pos: vec3<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
@@ -85,6 +89,7 @@ fn vertex(v: Vertex) -> VertexOutput {
|
||||
out.clip_position = view.clip_from_world * vec4<f32>(v.position, 1.0);
|
||||
// Eye-Z via the full affine transform; its rounding is yard-scale and harmless.
|
||||
out.view_z = -(view.view_from_world * vec4<f32>(v.position, 1.0)).z;
|
||||
out.world_pos = v.position; // MONKEY (p0 fog hook)
|
||||
#else
|
||||
// Cam-relative verts: view_from_world is [R | −R·cam] and the rebase already subtracted cam,
|
||||
// so only the rotation applies.
|
||||
@@ -95,6 +100,7 @@ fn vertex(v: Vertex) -> VertexOutput {
|
||||
) * v.position;
|
||||
out.clip_position = view.clip_from_view * vec4<f32>(view_pos, 1.0);
|
||||
out.view_z = -view_pos.z;
|
||||
out.world_pos = v.position + view.world_position; // MONKEY (p0 fog hook): undo the rebase
|
||||
#endif
|
||||
out.uv = v.uv;
|
||||
out.color = v.color;
|
||||
@@ -150,19 +156,19 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Scene fog: the world's linear fog (same start and end, planar eye-Z) in gamma space before
|
||||
// the blend; only its colour follows the per-blend policy in `wow_params.fog.x`.
|
||||
if (wow_light.fog_color.w > 0.5 && wow_params.fog.x > 0.5) {
|
||||
let denom = max(wow_light.fog_params.y - wow_light.fog_params.x, 0.001);
|
||||
let factor = clamp((wow_light.fog_params.y - in.view_z) / denom, 0.0, 1.0);
|
||||
var fog_rgb = wow_light.fog_color.xyz;
|
||||
if (wow_params.fog.x > 1.5 && wow_params.fog.x < 2.5) { fog_rgb = vec3<f32>(0.0); }
|
||||
else if (wow_params.fog.x > 2.5 && wow_params.fog.x < 3.5) { fog_rgb = vec3<f32>(1.0); }
|
||||
else if (wow_params.fog.x > 3.5) { fog_rgb = RAIN_FOG_GREY; }
|
||||
rgb = mix(fog_rgb, rgb, factor);
|
||||
// MONKEY (p0 fog hook): the shared fog law (fog_hook.wgsl); classic is bit-identical.
|
||||
rgb = fog_hook::apply_fog(rgb, fog_rgb, wow_light.fog_params.xy, in.view_z, in.world_pos,
|
||||
view.world_position, wow_params.fog.x < 1.5, wow_light.monkey);
|
||||
}
|
||||
// Rain's forced fog: grey over the draw's own start/end, whatever the scene fog; grey is
|
||||
// neutral under Mod2x, so this is the streaks' distance fade.
|
||||
if (wow_params.fog.y > 0.5) {
|
||||
let denom = max(wow_params.fog.w - wow_params.fog.z, 0.001);
|
||||
let factor = clamp((wow_params.fog.w - in.view_z) / denom, 0.0, 1.0);
|
||||
// MONKEY (p0 fog hook): the draw's own distance fade, always the classic linear law.
|
||||
let factor = fog_hook::fog_linear(in.view_z, wow_params.fog.zw);
|
||||
rgb = mix(RAIN_FOG_GREY, rgb, factor);
|
||||
}
|
||||
#ifdef BLEND_ADD
|
||||
|
||||
Reference in New Issue
Block a user