From 36d03ee462eb04a2bd318d37c8819bb03dc29f19 Mon Sep 17 00:00:00 2001 From: pkuzic Date: Fri, 25 Sep 2026 02:02:19 +0100 Subject: [PATCH] MONKEY (post): route emissive materials into HDR Co-Authored-By: GPT-5.6 Sol (Codex) --- LIGHTING.md | 2 ++ WATER.md | 1 + crates/benilla-app/assets/ui/OptionsFrame.xml | 31 ++++++++++++++-- crates/benilla-app/src/cvars.rs | 6 ++++ crates/benilla-app/src/dynamic_interior.rs | 10 +++++- crates/benilla-app/src/video.rs | 6 ++++ crates/benilla-assets/src/materials.rs | 2 ++ .../src/shaders/emissive_hook.wgsl | 36 +++++++++++++++++++ crates/benilla-assets/src/shaders/liquid.wgsl | 9 ++++- .../benilla-assets/src/shaders/wow_model.wgsl | 5 +++ .../src/lighting/global_light.rs | 13 +++++++ crates/benilla-world/src/lighting/mod.rs | 2 ++ .../benilla-world/src/shaders/static_gx.wgsl | 8 +++++ .../benilla-world/src/shaders/wow_effect.wgsl | 6 +++- 14 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 crates/benilla-assets/src/shaders/emissive_hook.wgsl diff --git a/LIGHTING.md b/LIGHTING.md index 2122d3df..f1852454 100644 --- a/LIGHTING.md +++ b/LIGHTING.md @@ -24,6 +24,7 @@ behind each constant. | Moon shadows | at night the same shadow rig re-aims at the moon and casts a faint shadow; dims only the night sky term, never point lights | `moonShadowStrength` | | Ground-effect spells | Flamestrike, Rain of Fire, Consecration, Flare and fire traps light the ground for their duration; frost and nature areas stay dark | `spellLightGain` | | Volumetric fog | near-field haze that converges on the zone fog colour (clear within 10 yd, full by 150 yd; mistier at dawn and in bad weather, faint indoors) and sun/moon light shafts through gaps, sampled from the shadow map; own fullscreen pass after the main pass (`benilla-app/src/volumetric_fog.rs`) | Advanced Graphics → Volumetric Fog (Off/Low/High), cvar `volumetricFog`, env `WOW_VOLFOG=0\|1\|2` | +| HDR emission and bloom | lit WMO windows, additive models/particles and magma can exceed display white; a soft-capped fullscreen halo is added before the faithful FFX clamp | Advanced Graphics → Bloom (Off/Low/High), cvar `bloom`; Off is the unchanged reference image, suggested High preset value 2 | | Night and interior level | global dimming of the night sky term and of interior ambient | `nightGain`, `interiorGain`, `interiorBakeFloor` | Players reach all of it from **Options -> Advanced Graphics** (a Lighting Quality preset Off / Low / Medium / High plus the individual rows; Off is the original client look). The dev build has a panel for all of it: **Ctrl+Shift+D → Lighting & shadows**, with Dim / Default / @@ -35,6 +36,7 @@ is its own module, documented in `WATER.md`. ## Where the code lives - **Shaders**: `crates/benilla-assets/src/shaders/shadow_hook.wgsl` (shared shadow sampling), + `crates/benilla-assets/src/shaders/emissive_hook.wgsl` (shared opt-in HDR multipliers), `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. diff --git a/WATER.md b/WATER.md index 9ee06550..4938656a 100644 --- a/WATER.md +++ b/WATER.md @@ -46,6 +46,7 @@ the technique is listed in the table below. Do not remove either. | Beach surf | a travelling wave front, a lace of foam that dissolves behind it, a swash sheet on the sand | tier | | Swimmers | wake foam, treading rings and a visual-only bob with the swell | tier | | Lava glow | magma surfaces emit warm point lights through the lighting module | Advanced Graphics → Lava Glow, `lavaLightGain` | +| Lava HDR | magma partially resists distance fog and feeds the screen-space halo | Advanced Graphics → Bloom, `bloom` (Off/Low/High) | Rivers deliberately have NO flow effect: a derived current was tried and removed at the owner's request. If it ever returns it should read the authored MCLQ flow records, not derive one. diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml index 71e19e82..bc03017f 100644 --- a/crates/benilla-app/assets/ui/OptionsFrame.xml +++ b/crates/benilla-app/assets/ui/OptionsFrame.xml @@ -494,6 +494,8 @@ lightingQuality = "Lighting Quality", -- MONKEY (volumetric fog): localised atmospheric quality row. volumetricFog = "Volumetric Fog", + -- MONKEY (post): HDR excess + halo quality. + bloom = "Bloom", fogOff = "Off", waterQuality = "Water Quality", waterClassic = "Classic", @@ -532,6 +534,9 @@ .. "Low, Medium and High use Enhanced water; mirror reflections stay opt-in. " .. "Changing a preset member shows Custom when no preset matches.", -- MONKEY (volumetric fog): explain cost and the shadow-map dependency. + -- MONKEY (post): Off guarantees the pre-HDR image. + BLOOM = + "Makes fire, lava, spells and lit windows carry a soft halo. Low uses a smaller blur chain; High is smoother. Off leaves the original image unchanged.", VOLUMETRIC_FOG = "Off disables volumetric fog. Low adds nearby haze and sun or moon shafts with fewer steps; High uses more steps for smoother shafts. Mist is heavier at dawn and in bad weather, and subtle indoors. Shafts require shadows; with shadows off only haze remains.", WATER_QUALITY = @@ -616,6 +621,8 @@ ruRU = { page = "Расширенная графика", lightingQuality = "Качество освещения", + -- MONKEY (post): Russian HDR excess + halo quality row. + bloom = "Свечение", -- MONKEY (volumetric fog): Russian atmospheric quality row. volumetricFog = "Объёмный туман", fogOff = "Выкл", @@ -657,6 +664,9 @@ .. "сохраняют ваши значения. «Низкое», «Среднее» и «Высокое» используют улучшенную воду; " .. "зеркальные отражения включаются отдельно. Изменение настройки набора показывает «Своё», " .. "если ни один набор больше не совпадает.", + -- MONKEY (post): localised bloom effect and Off contract. + BLOOM = + "Добавляет мягкое сияние огню, лаве, заклинаниям и освещённым окнам. Низкое качество использует короткое размытие, высокое — более плавное. Выключение сохраняет исходное изображение.", -- MONKEY (volumetric fog): localised effect and cost explanation. VOLUMETRIC_FOG = "Выкл отключает объёмный туман. Низкое добавляет дымку вблизи и лучи солнца или луны с меньшим числом шагов; Высокое использует больше шагов для более плавных лучей. Туман гуще на рассвете и в непогоду, а в помещениях слабее. Для лучей нужны тени; без теней остаётся только дымка.", @@ -795,7 +805,7 @@ -- three of eighteen shadow settings and no hint that the other fifteen existed. AdvancedGraphics = { -- MONKEY (volumetric fog): include in refresh, defaults and search. - "RowLightingQuality", "RowWaterQuality", "RowVolumetricFog", + "RowLightingQuality", "RowWaterQuality", "RowVolumetricFog", "RowBloom", "RowCharacterShadows", "RowWorldShadows", "RowShadowDistance", "RowShadowResolution", "RowMoonShadows", "RowInteriorLight", "RowInteriorShadows", "RowExteriorShadows", "RowTorchSoft", @@ -2602,11 +2612,28 @@ }) - + + + + OptionsRow_OnLoad(self, "bloom", BENILLA_ADVGFX.bloom, + "BENILLA_TOOLTIP_BLOOM") + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.fogOff, value = "0" }, + { text = BENILLA_ADVGFX.qLow, value = "1" }, + { text = BENILLA_ADVGFX.qHigh, value = "2" }, + }) + + + + + + + + OptionsRow_OnLoad(self, "characterShadows", BENILLA_ADVGFX.characterShadows, diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs index 31a7eced..66156e99 100644 --- a/crates/benilla-app/src/cvars.rs +++ b/crates/benilla-app/src/cvars.rs @@ -921,6 +921,8 @@ pub(crate) const REGISTERED: &[Registered] = &[ ), // MONKEY (volumetric fog): saved live tier; capture override stays session-only. ours("volumetricFog", "1", "benilla's own: near-field volumetric fog, 0 Off / 1 Low / 2 High"), + // MONKEY (post): tier 0 leaves every emissive site and the frame byte-identical. + ours("bloom", "0", "benilla's own: HDR emissive bloom, 0 Off / 1 Low / 2 High"), ours( "waterQuality", "1", @@ -2902,6 +2904,8 @@ mod tests { ("waterQuality", shadows.water_quality as f32), // MONKEY (volumetric fog): weld registry and renderer defaults. ("volumetricFog", shadows.volumetric_fog as f32), + // MONKEY (post): weld registry and renderer defaults. + ("bloom", shadows.bloom as f32), ("lavaLightGain", shadows.lava_light_gain), // The two sun lanes and the cascade they share. ("worldShadows", flag(shadows.world_shadows)), @@ -3133,6 +3137,8 @@ mod tests { "waterQuality" => video.water_quality as f32, // MONKEY (volumetric fog): verify presets reach the renderer. "volumetricFog" => video.volumetric_fog as f32, + // MONKEY (post): the post lane's live tier. + "bloom" => video.bloom as f32, "lavaLightGain" => video.lava_light_gain, "fireLightGain" => video.fire_light_gain, "moonShadowStrength" => video.moon_shadow_strength, diff --git a/crates/benilla-app/src/dynamic_interior.rs b/crates/benilla-app/src/dynamic_interior.rs index e34976f6..1fbb6fa8 100644 --- a/crates/benilla-app/src/dynamic_interior.rs +++ b/crates/benilla-app/src/dynamic_interior.rs @@ -25,7 +25,9 @@ //! //! Water quality and lava glow use the same guarded bridge into their renderer resources. use benilla_assets::WaterQuality; -use benilla_world::lighting::{DynamicInteriors, FireLightGain, LavaLightGain, MoonShadowStrength, SpellLightGain}; +use benilla_world::lighting::{ + DynamicInteriors, EmissiveTier, FireLightGain, LavaLightGain, MoonShadowStrength, SpellLightGain, +}; use bevy::prelude::*; use crate::video::VideoConfig; @@ -60,6 +62,8 @@ fn bridge( mut moon: ResMut, mut water: ResMut, mut lava: ResMut, + // MONKEY (post): bloom also arms the HDR source hooks through the shared light blob. + mut emissive: ResMut, ) { if water.0 != video.water_quality { water.0 = video.water_quality; @@ -67,6 +71,9 @@ fn bridge( if lava.0 != video.lava_light_gain { lava.0 = video.lava_light_gain; } + if emissive.0 != video.bloom { + emissive.0 = video.bloom; + } if fire.0 != video.fire_light_gain { fire.0 = video.fire_light_gain; } @@ -130,6 +137,7 @@ mod tests { .init_resource::() .init_resource::() .init_resource::() + .init_resource::() .add_plugins(DynamicInteriorPlugin); assert_eq!(app.world().resource::().0, VideoConfig::default().water_quality); assert_eq!(app.world().resource::().0, VideoConfig::default().lava_light_gain); diff --git a/crates/benilla-app/src/video.rs b/crates/benilla-app/src/video.rs index 07c17824..30a2b12f 100644 --- a/crates/benilla-app/src/video.rs +++ b/crates/benilla-app/src/video.rs @@ -222,6 +222,8 @@ pub(crate) struct VideoConfig { pub(crate) water_quality: u8, // MONKEY (volumetric fog): live camera raymarch tier: 0 Off, 1 Low, 2 High. pub(crate) volumetric_fog: u8, + // MONKEY (post): emissive HDR + bloom quality, 0 Off / 1 Low / 2 High. + pub(crate) bloom: u8, /// Brightness of lava lighting its surroundings, 0..4; 0 disables the glow. /// Published to `benilla_world::lighting::LavaLightGain` by `dynamic_interior::bridge`. pub(crate) lava_light_gain: f32, @@ -467,6 +469,8 @@ impl Default for VideoConfig { water_quality: 1, // MONKEY (volumetric fog): default to the inexpensive atmosphere. volumetric_fog: 1, + // MONKEY (post): opt-in; the future High graphics preset selects tier 2. + bloom: 0, lava_light_gain: 1.0, fire_flicker: 1.0, display: if windowed_env() { @@ -547,6 +551,8 @@ pub(crate) fn on_cvar( "waterquality" => cfg.water_quality = v.clamp(0.0, 2.0) as u8, // MONKEY (volumetric fog): constrain UI/console writes to supported tiers. "volumetricfog" => cfg.volumetric_fog = v.clamp(0.0, 2.0) as u8, + // MONKEY (post): constrain UI/console writes to the supported bloom tiers. + "bloom" => cfg.bloom = v.clamp(0.0, 2.0) as u8, "lavalightgain" => cfg.lava_light_gain = v.clamp(0.0, 4.0), "worldshadows" => cfg.world_shadows = ev.flag(), "charactershadows" => cfg.character_shadows = ev.flag(), diff --git a/crates/benilla-assets/src/materials.rs b/crates/benilla-assets/src/materials.rs index 18aa1682..fec812b8 100644 --- a/crates/benilla-assets/src/materials.rs +++ b/crates/benilla-assets/src/materials.rs @@ -29,6 +29,8 @@ 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 (post): shared opt-in HDR multipliers; tier 0 is an exact identity. + load_shader_library!(app, "shaders/emissive_hook.wgsl"); // MONKEY (enhanced water): the optional water module, `benilla::enhanced_water` (WATER.md). load_shader_library!(app, "shaders/enhanced_water.wgsl"); } diff --git a/crates/benilla-assets/src/shaders/emissive_hook.wgsl b/crates/benilla-assets/src/shaders/emissive_hook.wgsl new file mode 100644 index 00000000..8668b08d --- /dev/null +++ b/crates/benilla-assets/src/shaders/emissive_hook.wgsl @@ -0,0 +1,36 @@ +#define_import_path benilla::emissive_hook + +// MONKEY (post): the opt-in HDR seam shared by windows, additive models/effects and magma. +// `light_diffuse.w` was the reference pack's unused clamp marker (always 1); the CPU now adds +// the `bloom` tier, so 1 remains a byte-identical Off sentinel without growing the light blob. +const EMISSIVE_WMO_WINDOW: u32 = 0u; +const EMISSIVE_M2_ADD: u32 = 1u; +const EMISSIVE_PARTICLE_ADD: u32 = 2u; +const EMISSIVE_MAGMA: u32 = 3u; + +fn tier(packed_lane: f32) -> f32 { + return clamp(round(packed_lane - 1.0), 0.0, 2.0); +} + +fn emissive_boost( + color: vec3, + kind: u32, + packed_lane: f32, + authored_weight: f32, +) -> vec3 { + let t = tier(packed_lane); + if (t < 0.5) { + return color; + } + let high = step(1.5, t); + 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)); +} + +// Distant magma keeps part of its emissive body instead of converging completely to fog. +fn magma_fog_resist(packed_lane: f32) -> f32 { + let t = tier(packed_lane); + return select(0.0, mix(0.32, 0.48, step(1.5, t)), t > 0.5); +} diff --git a/crates/benilla-assets/src/shaders/liquid.wgsl b/crates/benilla-assets/src/shaders/liquid.wgsl index 1a9d72e7..36eb0d35 100644 --- a/crates/benilla-assets/src/shaders/liquid.wgsl +++ b/crates/benilla-assets/src/shaders/liquid.wgsl @@ -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 (post): shared tier-gated HDR emission and magma fog resistance. +#import benilla::emissive_hook @group(#{MATERIAL_BIND_GROUP}) @binding(100) var frames: texture_2d_array; @group(#{MATERIAL_BIND_GROUP}) @binding(101) var frames_samp: sampler; @@ -227,7 +229,12 @@ fn fragment(in: LiquidVsOut) -> @location(0) vec4 { // Magma/slime: the sheet is the opaque body, unmodulated (the ADT vertex has no colour, the WMO // one is `0xffffffff`) and unlit (lighting off on both paths), but fogged. if (w.kind.x > 0.5) { - return vec4(apply_fog(detail.rgb, in.world_position.xyz, in.room_fog), 1.0); + // MONKEY (post): distant magma keeps some authored body and crosses 1.0 for bloom. + let fogged = apply_fog(detail.rgb, in.world_position.xyz, in.room_fog); + let hot = emissive_hook::emissive_boost( + detail.rgb, emissive_hook::EMISSIVE_MAGMA, wow_light.light_diffuse.w, 1.0); + return vec4(mix(fogged, hot, + emissive_hook::magma_fog_resist(wow_light.light_diffuse.w)), 1.0); } // V, from the authored depth byte CPU-side: clamp(byte/42) on river/lake (LUT `0xc81768`, diff --git a/crates/benilla-assets/src/shaders/wow_model.wgsl b/crates/benilla-assets/src/shaders/wow_model.wgsl index 07705ba5..363d06e9 100644 --- a/crates/benilla-assets/src/shaders/wow_model.wgsl +++ b/crates/benilla-assets/src/shaders/wow_model.wgsl @@ -20,6 +20,8 @@ } // MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here. #import benilla::shadow_hook +// MONKEY (post): shared tier-gated HDR emission; Off is an exact identity. +#import benilla::emissive_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. @@ -1737,6 +1739,9 @@ fn fragment(in: WowVsOut, @builtin(front_facing) is_front: bool) -> WowFragOut { var out_rgb = rgb; if (is_additive) { out_rgb = out_rgb * faded_alpha; + // MONKEY (post): additive M2 cards become HDR before their framebuffer blend stacks them. + out_rgb = emissive_hook::emissive_boost( + out_rgb, emissive_hook::EMISSIVE_M2_ADD, wow_light.light_diffuse.w, 1.0); } // Mod (bit 7) and Mod2x (bit 8) read no source alpha, so the fade rides the colour as in the // reference: texenv preset 5, `mix(prev.rgb, tex.rgb, prev.a)`, with the primary colour forced diff --git a/crates/benilla-world/src/lighting/global_light.rs b/crates/benilla-world/src/lighting/global_light.rs index b3373de0..4e63fa07 100644 --- a/crates/benilla-world/src/lighting/global_light.rs +++ b/crates/benilla-world/src/lighting/global_light.rs @@ -474,6 +474,11 @@ impl Default for FireLightGain { } } +/// MONKEY (post): live `bloom` tier. The packer adds it to `light_diffuse.w`, whose old value 1 +/// was an unread clamp marker, so the 8528-byte shared buffer and every existing decode stay put. +#[derive(Resource, Clone, Copy, Default, PartialEq, Eq, Debug)] +pub struct EmissiveTier(pub u8); + /// MONKEY (spellLightGain): marks a `PointLight` that a SPELL EFFECT invented — a kit's aura glow, /// a missile's core, an impact flash, a firework shell's burst. The app's own /// `entities::spell_fx::SpellLight` carries the envelope, the mode and the budget; this is the @@ -641,6 +646,8 @@ pub(super) fn register(app: &mut App) { .init_resource::() // MONKEY (fire GO lights): the live gain on synthesised fire lights. .init_resource::() + // MONKEY (post): opt-in HDR emission, packed into an existing float lane. + .init_resource::() // MONKEY (spellLightGain): and the one on spell-effect lights, which overrides it. .init_resource::() .init_resource::() @@ -1286,6 +1293,8 @@ fn build_light_data( shadow: ShadowLanes, // MONKEY (dynamic interiors): the interior lane's on/off + live knobs, packed for `static_gx.wgsl`. dynamic_interiors: Res, + // MONKEY (post): the 0/1/2 tier rides the unused `light_diffuse.w` marker. + emissive: Res, // MONKEY (fire GO lights): the live gain on synthesised fire lights (0 = the lane off). fire_gain: Res, // MONKEY (spellLightGain): and the spell lane's own, which overrides it on a spell row. @@ -1353,6 +1362,8 @@ fn build_light_data( // this packs (rows 0/1, the SH block, the sun's DC redistribution) is LINEAR in that triple, so // one multiply at the input dims all of them consistently and no derived row can be missed. pack_model_core_rows(rows, night_dim(l.ambient), night_dim(l.diffuse), l.sun_dir); + // MONKEY (post): old value 1 remains tier Off; no existing shader consumes this marker. + rows[1][3] = 1.0 + emissive.0.min(2) as f32; // MONKEY (world shadows): pack the world-shadow lane flag into the free `sh_c16.w` lane. The // MCSH terrain-shadow suppression in `terrain.wgsl` keys on THIS — not on the mere presence of // a shadow sun — so character-only shadows (sun present, world lane off) keep the baked MCSH. @@ -2037,6 +2048,8 @@ mod tests { .init_resource::() .init_resource::() .init_resource::() + // MONKEY (post): the packer reads the live HDR tier too. + .init_resource::() .init_resource::() .add_systems(Update, build_light_data); app.world_mut() diff --git a/crates/benilla-world/src/lighting/mod.rs b/crates/benilla-world/src/lighting/mod.rs index 027a4ce0..7f745974 100644 --- a/crates/benilla-world/src/lighting/mod.rs +++ b/crates/benilla-world/src/lighting/mod.rs @@ -51,6 +51,8 @@ pub use global_light::WorldPointLight; // MONKEY (spellLightGain): the spell lane's marker + its live gain — the two-word world-side // shadow of benilla-app's own `SpellLight` lifecycle, and the dial the packer folds over it. pub use global_light::{SpellFxLight, SpellLightGain}; +// MONKEY (post): live 0/1/2 emissive tier packed without growing the shared light blob. +pub use global_light::EmissiveTier; pub use global_light::{ room_claim_bytes, CLAIM_EXT_OK, LIT_ROOM_EXT_DENY, ROOM_CLAIM_MAX, ROOM_CLAIM_STRIDE, }; diff --git a/crates/benilla-world/src/shaders/static_gx.wgsl b/crates/benilla-world/src/shaders/static_gx.wgsl index ebe78d5a..41e12b7c 100644 --- a/crates/benilla-world/src/shaders/static_gx.wgsl +++ b/crates/benilla-world/src/shaders/static_gx.wgsl @@ -10,6 +10,8 @@ } // MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here. #import benilla::shadow_hook +// MONKEY (post): shared tier-gated HDR emission; Off is an exact identity. +#import benilla::emissive_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,6 +1982,12 @@ fn fragment(in: GxVsOut) -> @location(0) vec4 { let factor = clamp((fog_span.y - eye_z) / denom, 0.0, 1.0); rgb = mix(fog_color.xyz, rgb, factor); } + // 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); + } // Gamma-space output; alpha pinned 1.0, every draw here is opaque. return vec4(rgb, 1.0); } diff --git a/crates/benilla-world/src/shaders/wow_effect.wgsl b/crates/benilla-world/src/shaders/wow_effect.wgsl index f6654dd9..abf918a9 100644 --- a/crates/benilla-world/src/shaders/wow_effect.wgsl +++ b/crates/benilla-world/src/shaders/wow_effect.wgsl @@ -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 (post): shared tier-gated HDR emission; Off is an exact identity. +#import benilla::emissive_hook // Prefix of `lighting::global_light`'s buffer; keep in sync with wow_model.wgsl's copy. struct WowLight { @@ -160,7 +162,9 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { } #ifdef BLEND_ADD // Premultiplied in gamma, so stacked quads sum like the reference's bytes. - return vec4(rgb * c.a, 0.0); + // MONKEY (post): boost before the additive framebuffer blend, never after the stack. + return vec4(emissive_hook::emissive_boost( + rgb * c.a, emissive_hook::EMISSIVE_PARTICLE_ADD, wow_light.light_diffuse.w, 1.0), 0.0); #else #ifdef BLEND_OPAQUE return vec4(rgb, 1.0);