MONKEY (p0): MonkeyFrame block appended to the shared light buffer
16 vec4 rows (256 B) after the point-light table, so no earlier offset moves (per-frame blob 8528 -> 8784 B, size test updated). Rust side: the `lighting::MonkeyFrame` resource any system may write, packed each frame by `global_light::pack_monkey_frame` (which also fills time_of_day / night). WGSL side: `benilla::monkey_frame::MonkeyFrame`, declared as `monkey` after `points` in every WowLight mirror (terrain, wow_model, static_gx, liquid, enhanced_water, wow_effect, wdl). All zero = no visual change. Layout in LIGHTING.md "Invariants". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
da0f1a0d80
commit
f62a9eae5b
+22
-2
@@ -52,8 +52,28 @@ is its own module, documented in `WATER.md`.
|
||||
|
||||
## Invariants worth knowing before editing
|
||||
|
||||
- The shared light buffer stays 8528 bytes and the torch table 6416 bytes; both are mirrored in
|
||||
three shaders and pinned by tests.
|
||||
- The shared light buffer's per-frame blob is 8784 bytes and the torch table 6416 bytes; both are
|
||||
mirrored in the shaders and pinned by tests. The blob is 21 header rows (336 B), the 256-slot
|
||||
point-light table (8192 B), then the MONKEY **MonkeyFrame** block (16 rows, 256 B; nothing before
|
||||
it moves). Every `WowLight` mirror (terrain, wow_model, static_gx, liquid, enhanced_water,
|
||||
wow_effect, wdl) declares it as `monkey: monkey_frame::MonkeyFrame` after `points`:
|
||||
|
||||
| row | x | y | z | w |
|
||||
|---|---|---|---|---|
|
||||
| `fog0` | height_fog_density | height_fog_height | height_fog_falloff | curve_blend |
|
||||
| `fog1` | sun_fog r | g | b | sun_fog_strength |
|
||||
| `fog2` | end_fog r | g | b | end_fog_distance |
|
||||
| `fog3` | fog_model (0 classic, 1 modern) | sun_fog_angle | 0 | 0 |
|
||||
| `wind0` | dir_x | dir_y | speed | gust |
|
||||
| `wind1` | time_s | sway_strength | grass_strength | tree_strength |
|
||||
| `wet0` | rain_rate | wetness | ripple_time_s | snow |
|
||||
| `misc` | bender_count | time_of_day 0..1 | night 0..1 | 0 |
|
||||
| `benders[8]` | world x | world y | world z | radius |
|
||||
|
||||
Positions are Bevy world space (Y up, yards; WoW `(x, y, z)` = Bevy `(-y, z, -x)`), the wind
|
||||
direction is a unit vector in world XZ. Lanes write the `MonkeyFrame` resource
|
||||
(`benilla-world/src/lighting/monkey_frame.rs`); `global_light::pack_monkey_frame` packs it after
|
||||
the point table and fills `time_of_day` / `night` itself. All zero = no visual change.
|
||||
- World lights use upstream's `WorldPointLight`, never Bevy's `PointLight`.
|
||||
- WGSL only fails at pipeline creation, so a shader edit is verified by running a world scene, not
|
||||
by `cargo check`.
|
||||
|
||||
@@ -29,6 +29,9 @@ pub fn register_shaders(app: &mut App) {
|
||||
// (loaded eagerly so `#import benilla::shadow_hook` resolves in terrain/model/static_gx). This is
|
||||
// the ONE place the realtime directional-shadow term lives; the receivers just call into it.
|
||||
load_shader_library!(app, "shaders/shadow_hook.wgsl");
|
||||
// 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 (enhanced water): the optional water module, `benilla::enhanced_water` (WATER.md).
|
||||
load_shader_library!(app, "shaders/enhanced_water.wgsl");
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
// 106 the opaque scene COLOUR, copied beside the depth: what the refraction looks through
|
||||
|
||||
#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
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(103) var scene_depth: texture_2d<f32>;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(106) var scene_colour: texture_2d<f32>;
|
||||
@@ -55,6 +57,8 @@ struct WaterLight {
|
||||
wmo_fog_params: vec4<f32>, // 19
|
||||
point_count: vec4<f32>, // 20 live count + point-light controls
|
||||
points: array<vec4<f32>, 512>, // 21+ position/range, colour/lane pairs
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
};
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(105) var<storage, read> water_light: WaterLight;
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
}
|
||||
// MONKEY (enhanced water): the optional water module - see enhanced_water.wgsl and WATER.md.
|
||||
#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
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(100) var frames: texture_2d_array<f32>;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var frames_samp: sampler;
|
||||
@@ -60,6 +62,10 @@ struct WowLight {
|
||||
_grade: vec4<f32>, // 17
|
||||
wmo_fog_color: vec4<f32>, // 18 rgb = interior fog (block 2); w = enable
|
||||
wmo_fog_params: vec4<f32>, // 19 x = start yd; y = end yd
|
||||
_point_count: vec4<f32>, // 20 point-light count, unread here
|
||||
_points: array<vec4<f32>, 512>, // 21+ point-light table, unread here
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
};
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(90) var<storage, read> wow_light: WowLight;
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#define_import_path benilla::monkey_frame
|
||||
|
||||
// MONKEY (p0 MonkeyFrame): the graphics programme's per-frame block, 16 rows (256 B) appended to
|
||||
// the shared light buffer right after the point-light table (`lighting::global_light`, packed from
|
||||
// the `MonkeyFrame` resource in `lighting/monkey_frame.rs`). Every receiver's `WowLight` mirror
|
||||
// declares it as `monkey: monkey_frame::MonkeyFrame` after `points`, so the offsets of everything
|
||||
// before it never move. All zero = no visual change: every reader treats zero as "feature off".
|
||||
//
|
||||
// Coordinates are Bevy world space (Y up, 1 unit = 1 yd), the space of the receivers'
|
||||
// `world_position`: WoW `(x, y, z)` is Bevy `(-y, z, -x)` (`benilla_assets::coords::wow_to_bevy`).
|
||||
struct MonkeyFrame {
|
||||
fog0: vec4<f32>, // height_fog_density, height_fog_height (world Y, yd), height_fog_falloff, curve_blend
|
||||
fog1: vec4<f32>, // sun_fog_rgb (gamma 0..1), sun_fog_strength
|
||||
fog2: vec4<f32>, // end_fog_rgb (gamma 0..1), end_fog_distance (yd)
|
||||
fog3: vec4<f32>, // fog_model (0 classic, 1 modern), sun_fog_angle (cos), 0, 0
|
||||
wind0: vec4<f32>, // dir_x, dir_y (unit, world XZ: .x = world x, .y = world z), speed (yd/s), gust 0..1
|
||||
wind1: vec4<f32>, // time_s, sway_strength, grass_strength, tree_strength
|
||||
wet0: vec4<f32>, // rain_rate 0..1, wetness 0..1, ripple_time_s, snow 0..1
|
||||
misc: vec4<f32>, // bender_count, time_of_day 0..1 (packer), night 0..1 (packer), 0
|
||||
benders: array<vec4<f32>, 8>, // world xyz (Bevy space) + radius (yd); the first bender_count are live
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
}
|
||||
// MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here.
|
||||
#import benilla::shadow_hook
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
|
||||
@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>;
|
||||
@@ -56,6 +58,8 @@ struct WowLight {
|
||||
// reach in yards) for an INTERIOR one. Terrain consumes only the exterior half.
|
||||
point_count: vec4<f32>,
|
||||
points: array<vec4<f32>, 512>,
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
};
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(90) var<storage, read> wow_light: WowLight;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
mesh_view_bindings::view,
|
||||
}
|
||||
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
|
||||
/// 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).
|
||||
const WDL_OVERLAP: f32 = 33.0;
|
||||
@@ -34,6 +37,11 @@ struct WowLight {
|
||||
_light_spec: vec4<f32>, // 3
|
||||
fog_color: vec4<f32>, // 4 rgb = Light.dbc row 7 (gamma 0..1); w = enable (>0.5 ⇒ blend)
|
||||
fog_params: vec4<f32>, // 5 x/y = the SCENE fog start/end yd (unread here — the hull has its own pair); z = the signed directional-shadow weight (MONKEY moon shadows: +sun / -moon; unread here); w = farclip wall (0 ⇒ off)
|
||||
// MONKEY (p0 MonkeyFrame): rows 6-20 and the point table, unread here, so the block lines up.
|
||||
_rows_6_20: array<vec4<f32>, 15>,
|
||||
_points: array<vec4<f32>, 512>,
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
};
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(90) var<storage, read> w: WowLight;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
}
|
||||
// MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here.
|
||||
#import benilla::shadow_hook
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
|
||||
// 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.
|
||||
@@ -112,6 +114,8 @@ struct WowLight {
|
||||
// packed reach is always ≥ 1 yd and can never be mistaken for the exterior 0.
|
||||
point_count: vec4<f32>,
|
||||
points: array<vec4<f32>, 512>,
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
// The interior-prop SH probes, 7 rows per slot (`MAX_PROP_PROBES` = 8192 slots). Only this
|
||||
// shader declares this tail; the other shaders bind the same buffer by its prefix.
|
||||
prop_probes: array<vec4<f32>, 57344>,
|
||||
|
||||
@@ -66,6 +66,9 @@ use crate::view::WorldCamera;
|
||||
struct LightStd430 {
|
||||
rows: [[f32; 4]; LIGHT_HEADER_ROWS],
|
||||
points: [[f32; 4]; 2 * MAX_POINT_LIGHTS],
|
||||
// MONKEY (p0 MonkeyFrame): the programme block, appended AFTER the point table so no earlier
|
||||
// offset moves (8528 -> 8784 B); packed by [`pack_monkey_frame`] from [`super::MonkeyFrame`].
|
||||
monkey: [[f32; 4]; super::monkey_frame::MONKEY_FRAME_ROWS],
|
||||
}
|
||||
|
||||
/// Header row count of the layout above (rows 0..=20), which every light blob sizes against.
|
||||
@@ -615,6 +618,7 @@ impl Default for WowLightData {
|
||||
Self(LightStd430 {
|
||||
rows: [[0.0; 4]; 21],
|
||||
points: [[0.0; 4]; 2 * MAX_POINT_LIGHTS],
|
||||
monkey: [[0.0; 4]; super::monkey_frame::MONKEY_FRAME_ROWS],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -644,6 +648,8 @@ pub(super) fn register(app: &mut App) {
|
||||
// MONKEY (spellLightGain): and the one on spell-effect lights, which overrides it.
|
||||
.init_resource::<SpellLightGain>()
|
||||
.init_resource::<super::prop_probes::PropProbeExtract>()
|
||||
// MONKEY (p0 MonkeyFrame): the per-frame programme parameters any system may write.
|
||||
.init_resource::<super::MonkeyFrame>()
|
||||
.add_plugins(ExtractResourcePlugin::<WowLightData>::default())
|
||||
.add_plugins(ExtractResourcePlugin::<RoomClaimTable>::default())
|
||||
.add_plugins(ExtractResourcePlugin::<SharedLightBuffer>::default())
|
||||
@@ -656,7 +662,8 @@ pub(super) fn register(app: &mut App) {
|
||||
// a light that has stood for a frame is packed on this frame's verdict. Its writes go
|
||||
// through `Commands`, so a NEWLY spawned light is still packed on the fail-safe
|
||||
// fallback for one frame — see that fallback's note in `build_light_data`.
|
||||
(classify_light_lanes, build_light_data)
|
||||
// MONKEY (p0 MonkeyFrame): the programme block is packed right after the table.
|
||||
(classify_light_lanes, build_light_data, pack_monkey_frame)
|
||||
.chain()
|
||||
.after(bevy::transform::TransformSystems::Propagate)
|
||||
.after(super::update_time_lighting),
|
||||
@@ -699,6 +706,22 @@ pub(super) fn per_frame_blob_bytes() -> u64 {
|
||||
std::mem::size_of::<LightStd430>() as u64
|
||||
}
|
||||
|
||||
/// MONKEY (p0 MonkeyFrame): copies [`super::MonkeyFrame`] into the block after the point table,
|
||||
/// plus the two clock fields the packer owns (`misc.y` time of day 0..1, `misc.z` night 0..1, the
|
||||
/// same dusk ramp `nightGain` uses). Writes through `ResMut` only when a row moved.
|
||||
fn pack_monkey_frame(
|
||||
frame: Res<super::MonkeyFrame>,
|
||||
clock: Res<super::GameClock>,
|
||||
light: Res<WowLighting>,
|
||||
mut data: ResMut<WowLightData>,
|
||||
) {
|
||||
let night = 1.0 - sun_shadow_strength(light.celestial_dir.y);
|
||||
let packed = frame.pack(clock.minute as f32 / 1440.0, night);
|
||||
if data.0.monkey != packed {
|
||||
data.0.monkey = packed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Pack the resolved [`WowLighting`] (+ the global fog-disable toggle and the view farclip) into the
|
||||
/// std430 blob. The `.w` lanes carry the faithful invariants the shaders expect (Mod2x 1.0, clamp on,
|
||||
/// terrain shininess 20, fog-enable, farclip wall); the model SH coeffs and both water swatches are
|
||||
@@ -2626,7 +2649,8 @@ mod tests {
|
||||
assert_eq!(MAX_LIVE_POINT_LIGHTS, 255, "255 is EXT_SEL_EMPTY in the three shaders");
|
||||
assert_eq!(MAX_LIVE_POINT_LIGHTS, MAX_POINT_LIGHTS - 1);
|
||||
// 21 header rows + 2 x 256 point rows, 16 B each.
|
||||
assert_eq!(per_frame_blob_bytes(), 8528, "the mirrored blob must not change size");
|
||||
// MONKEY (p0 MonkeyFrame): 8528 + the 256-byte programme block.
|
||||
assert_eq!(per_frame_blob_bytes(), 8784, "the mirrored blob must not change size");
|
||||
}
|
||||
|
||||
/// MONKEY (enclosed day floor): `interiorDaylight` rides the FRACTION of the interior lane's
|
||||
|
||||
@@ -11,6 +11,8 @@ mod daylight; // MONKEY (daylight fixtures): the sun as an interior-lane light i
|
||||
mod daynight; // the two sun directions + day/night interp + the dawn/dusk warp curve
|
||||
mod flicker; // MONKEY (flame flicker): the per-light fire wobble folded in at pack time
|
||||
mod global_light; // the one shared global-light storage buffer (replaces the per-material push)
|
||||
mod monkey_frame; // MONKEY (p0 MonkeyFrame): the programme's per-frame block after the point table
|
||||
pub use monkey_frame::{FogModel, MonkeyFrame, MAX_BENDERS, MONKEY_FRAME_ROWS};
|
||||
mod lava_light; // MONKEY (lava light): magma surface fixtures and their independent gain
|
||||
pub use lava_light::{LavaLight, LavaLightGain};
|
||||
mod prop_probes; // the per-instance interior-prop SH probe table (slot ↔ MeshTag payload)
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
//! MONKEY (p0 MonkeyFrame): the graphics programme's per-frame parameter block.
|
||||
//!
|
||||
//! One resource, [`MonkeyFrame`], that any system may write (fog model, wind, wetness, benders).
|
||||
//! The light packer (`global_light::pack_monkey_frame`) copies it every frame into 16 rows
|
||||
//! appended to the shared light buffer right after the point-light table, so the offsets of every
|
||||
//! row before it never move. The WGSL mirror is `benilla::monkey_frame::MonkeyFrame`
|
||||
//! (`benilla-assets/src/shaders/monkey_frame.wgsl`); every receiver's `WowLight` struct declares
|
||||
//! it as its `monkey` member.
|
||||
//!
|
||||
//! All zero (the default) is "every feature off": nothing reads a zero field as a change, so the
|
||||
//! image is the same as before the block existed.
|
||||
//!
|
||||
//! Layout (16 × `vec4<f32>`, 256 B):
|
||||
//!
|
||||
//! | row | xyz / x,y,z | w |
|
||||
//! |---|---|---|
|
||||
//! | 0 `fog0` | height_fog_density, height_fog_height, height_fog_falloff | curve_blend |
|
||||
//! | 1 `fog1` | sun_fog_rgb | sun_fog_strength |
|
||||
//! | 2 `fog2` | end_fog_rgb | end_fog_distance |
|
||||
//! | 3 `fog3` | fog_model (0 classic, 1 modern), sun_fog_angle, 0 | 0 |
|
||||
//! | 4 `wind0` | dir_x, dir_y, speed | gust |
|
||||
//! | 5 `wind1` | time_s, sway_strength, grass_strength | tree_strength |
|
||||
//! | 6 `wet0` | rain_rate, wetness, ripple_time_s | snow |
|
||||
//! | 7 `misc` | bender_count, time_of_day 0..1, night 0..1 | 0 |
|
||||
//! | 8-15 `benders` | world x, y, z | radius |
|
||||
//!
|
||||
//! Coordinates are Bevy world space (Y up, 1 unit = 1 yd), the space the receivers'
|
||||
//! `world_position` is in: WoW `(x, y, z)` → Bevy `(-y, z, -x)` ([`benilla_assets::coords`]).
|
||||
//! The wind direction is a unit vector in the world XZ plane: `dir_x` = world x, `dir_y` = world z.
|
||||
//! `time_of_day` and `night` are filled by the packer from the game clock and the celestial sun;
|
||||
//! every other field belongs to the lane that owns the feature.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Rows in the appended block.
|
||||
pub const MONKEY_FRAME_ROWS: usize = 16;
|
||||
|
||||
/// How many benders (grass/foliage pushers: the player and nearby units) the block carries.
|
||||
pub const MAX_BENDERS: usize = 8;
|
||||
|
||||
/// The fog model selector, `fog3.x`.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum FogModel {
|
||||
/// Today's linear 1.12 fog, byte-identical.
|
||||
#[default]
|
||||
Classic,
|
||||
/// The FOG lane's modern model (height / sun / end-colour / curve fog).
|
||||
Modern,
|
||||
}
|
||||
|
||||
/// The per-frame programme parameters. Write the fields your lane owns through
|
||||
/// `ResMut<MonkeyFrame>`; leave the rest alone. See the module docs for the packed layout.
|
||||
#[derive(Resource, Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct MonkeyFrame {
|
||||
// ── FOG lane (rows 0-3) ──
|
||||
pub fog_model: FogModel,
|
||||
pub height_fog_density: f32,
|
||||
/// World Y (Bevy space, yd) of the height-fog plane.
|
||||
pub height_fog_height: f32,
|
||||
pub height_fog_falloff: f32,
|
||||
pub fog_curve_blend: f32,
|
||||
/// Gamma 0..1.
|
||||
pub sun_fog_rgb: [f32; 3],
|
||||
pub sun_fog_strength: f32,
|
||||
/// Cosine threshold of the sun-fog lobe.
|
||||
pub sun_fog_angle: f32,
|
||||
/// Gamma 0..1.
|
||||
pub end_fog_rgb: [f32; 3],
|
||||
pub end_fog_distance: f32,
|
||||
// ── WIND lane (rows 4-5) ──
|
||||
/// Unit vector in the world XZ plane (`[x, z]`).
|
||||
pub wind_dir: [f32; 2],
|
||||
/// Yards per second.
|
||||
pub wind_speed: f32,
|
||||
pub wind_gust: f32,
|
||||
/// The wind clock in seconds (the lane owns it, so a capture can pin it).
|
||||
pub wind_time_s: f32,
|
||||
pub sway_strength: f32,
|
||||
pub grass_strength: f32,
|
||||
pub tree_strength: f32,
|
||||
// ── WET lane (row 6) ──
|
||||
pub rain_rate: f32,
|
||||
pub wetness: f32,
|
||||
pub ripple_time_s: f32,
|
||||
pub snow: f32,
|
||||
// ── benders (rows 7.x, 8-15) ──
|
||||
/// Bender slots; only the first `min(bender_count, MAX_BENDERS)` are packed. `[x, y, z, radius]` in
|
||||
/// Bevy world space.
|
||||
pub benders: [[f32; 4]; MAX_BENDERS],
|
||||
pub bender_count: u32,
|
||||
}
|
||||
|
||||
impl MonkeyFrame {
|
||||
/// The 16 packed rows. `time_of_day` (0..1) and `night` (0..1) are the packer's own inputs.
|
||||
pub fn pack(&self, time_of_day: f32, night: f32) -> [[f32; 4]; MONKEY_FRAME_ROWS] {
|
||||
let mut rows = [[0.0f32; 4]; MONKEY_FRAME_ROWS];
|
||||
let model = match self.fog_model {
|
||||
FogModel::Classic => 0.0,
|
||||
FogModel::Modern => 1.0,
|
||||
};
|
||||
rows[0] = [
|
||||
self.height_fog_density,
|
||||
self.height_fog_height,
|
||||
self.height_fog_falloff,
|
||||
self.fog_curve_blend,
|
||||
];
|
||||
let s = self.sun_fog_rgb;
|
||||
rows[1] = [s[0], s[1], s[2], self.sun_fog_strength];
|
||||
let e = self.end_fog_rgb;
|
||||
rows[2] = [e[0], e[1], e[2], self.end_fog_distance];
|
||||
rows[3] = [model, self.sun_fog_angle, 0.0, 0.0];
|
||||
rows[4] = [self.wind_dir[0], self.wind_dir[1], self.wind_speed, self.wind_gust];
|
||||
rows[5] = [self.wind_time_s, self.sway_strength, self.grass_strength, self.tree_strength];
|
||||
rows[6] = [self.rain_rate, self.wetness, self.ripple_time_s, self.snow];
|
||||
let n = (self.bender_count as usize).min(MAX_BENDERS);
|
||||
rows[7] = [n as f32, time_of_day, night, 0.0];
|
||||
rows[8..8 + n].copy_from_slice(&self.benders[..n]);
|
||||
rows
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_default_frame_packs_to_zero_but_the_clock() {
|
||||
let rows = MonkeyFrame::default().pack(0.0, 0.0);
|
||||
assert!(rows.iter().flatten().all(|v| *v == 0.0));
|
||||
let rows = MonkeyFrame::default().pack(0.5, 1.0);
|
||||
assert_eq!(rows[7], [0.0, 0.5, 1.0, 0.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fields_land_in_their_documented_rows() {
|
||||
let f = MonkeyFrame {
|
||||
fog_model: FogModel::Modern,
|
||||
height_fog_density: 1.0,
|
||||
height_fog_height: 2.0,
|
||||
height_fog_falloff: 3.0,
|
||||
fog_curve_blend: 4.0,
|
||||
sun_fog_rgb: [5.0, 6.0, 7.0],
|
||||
sun_fog_strength: 8.0,
|
||||
end_fog_rgb: [9.0, 10.0, 11.0],
|
||||
end_fog_distance: 12.0,
|
||||
sun_fog_angle: 13.0,
|
||||
wind_dir: [14.0, 15.0],
|
||||
wind_speed: 16.0,
|
||||
wind_gust: 17.0,
|
||||
wind_time_s: 18.0,
|
||||
sway_strength: 19.0,
|
||||
grass_strength: 20.0,
|
||||
tree_strength: 21.0,
|
||||
rain_rate: 22.0,
|
||||
wetness: 23.0,
|
||||
ripple_time_s: 24.0,
|
||||
snow: 25.0,
|
||||
benders: [[26.0, 27.0, 28.0, 29.0]; MAX_BENDERS],
|
||||
bender_count: 20, // clamped to MAX_BENDERS
|
||||
};
|
||||
let r = f.pack(0.25, 0.75);
|
||||
assert_eq!(r[0], [1.0, 2.0, 3.0, 4.0]);
|
||||
assert_eq!(r[1], [5.0, 6.0, 7.0, 8.0]);
|
||||
assert_eq!(r[2], [9.0, 10.0, 11.0, 12.0]);
|
||||
assert_eq!(r[3], [1.0, 13.0, 0.0, 0.0]);
|
||||
assert_eq!(r[4], [14.0, 15.0, 16.0, 17.0]);
|
||||
assert_eq!(r[5], [18.0, 19.0, 20.0, 21.0]);
|
||||
assert_eq!(r[6], [22.0, 23.0, 24.0, 25.0]);
|
||||
assert_eq!(r[7], [8.0, 0.25, 0.75, 0.0]);
|
||||
assert_eq!(r[15], [26.0, 27.0, 28.0, 29.0]);
|
||||
}
|
||||
|
||||
/// The WGSL mirror declares the same 16 rows; a member added on one side only would shift
|
||||
/// every bender.
|
||||
#[test]
|
||||
fn the_wgsl_mirror_has_sixteen_rows() {
|
||||
let src = include_str!("../../../benilla-assets/src/shaders/monkey_frame.wgsl");
|
||||
let body = src.split_once("struct MonkeyFrame {").unwrap().1;
|
||||
let body = body.split_once("\n}").unwrap().0;
|
||||
let mut rows = 0;
|
||||
for line in body.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with("//")) {
|
||||
if line.contains("array<vec4<f32>, 8>") {
|
||||
rows += 8;
|
||||
} else if line.contains("vec4<f32>") {
|
||||
rows += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(rows, MONKEY_FRAME_ROWS);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@
|
||||
}
|
||||
// MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here.
|
||||
#import benilla::shadow_hook
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
|
||||
// Group 0 is Bevy's standard mesh-view bind group (view matrices, directional-light records and
|
||||
// the shadow textures the retained pass reads).
|
||||
@@ -40,6 +42,8 @@ struct WowLight {
|
||||
// yards. wow_model.wgsl owns the full note.
|
||||
point_count: vec4<f32>,
|
||||
points: array<vec4<f32>, 512>,
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
// lighting::prop_probes: 8192 slots of 7 rows; the buffer's later regions are not mirrored.
|
||||
prop_probes: array<vec4<f32>, 57344>,
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// - BLEND_MOD2X: (rgb, 1) under (Dst, Src) = `2·src·dst`, rain's state; reads no alpha.
|
||||
|
||||
#import bevy_render::view::View
|
||||
// MONKEY (p0 MonkeyFrame): the programme block's struct, mirrored after the point table.
|
||||
#import benilla::monkey_frame
|
||||
|
||||
// Prefix of `lighting::global_light`'s buffer; keep in sync with wow_model.wgsl's copy.
|
||||
struct WowLight {
|
||||
@@ -33,6 +35,11 @@ struct WowLight {
|
||||
sh_c16: vec4<f32>,
|
||||
_water: array<vec4<f32>, 4>,
|
||||
grade: vec4<f32>,
|
||||
// MONKEY (p0 MonkeyFrame): rows 18-20 and the point table, unread here, so the block lines up.
|
||||
_rows_18_20: array<vec4<f32>, 3>,
|
||||
_points: array<vec4<f32>, 512>,
|
||||
// MONKEY (p0 MonkeyFrame): the programme block after the point table (monkey_frame.wgsl).
|
||||
monkey: monkey_frame::MonkeyFrame,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> view: View;
|
||||
|
||||
Reference in New Issue
Block a user