MONKEY (ao): diagnostic views, tuned gain/bias/strength, longer distance fade for outdoor contacts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dd0bb2bba9
commit
9eda0a738f
+1
-1
@@ -24,7 +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` |
|
||||
| Ambient occlusion | soft contact shadows under props, in wall-floor corners and around feet; depth-only SSAO at half resolution (depth-reconstructed normals, 6/12 spiral taps, 4x4 depth-aware blur, bilateral upsample) multiplied into the opaque scene after the opaque pass and before the water copy, so water, particles and UI are untouched; fades out 35-80 yd, spares sky and bright pixels (`benilla-app/src/ssao.rs`, `benilla-app/src/shaders/ssao.wgsl`) | Advanced Graphics → Ambient Occlusion (Off/Low/High; default Off, High preset 2), cvar `ambientOcclusion`, env `WOW_AO=0\|1\|2`, `WOW_AO_DEBUG=1` shows the AO term alone |
|
||||
| Ambient occlusion | soft contact shadows under props, in wall-floor corners and around feet; depth-only SSAO at half resolution (depth-reconstructed normals, 6/12 spiral taps, 4x4 depth-aware blur, bilateral upsample) multiplied into the opaque scene after the opaque pass and before the water copy, so water, particles and UI are untouched; fades out 45-90 yd (Low) / 60-120 yd (High), spares sky and bright pixels (`benilla-app/src/ssao.rs`, `benilla-app/src/shaders/ssao.wgsl`) | Advanced Graphics → Ambient Occlusion (Off/Low/High; default Off, High preset 2), cvar `ambientOcclusion`, env `WOW_AO=0\|1\|2`, `WOW_AO_DEBUG=1..4` diagnostic views (factor, protection, distance, raw occlusion), `WOW_AO_GAIN`/`_RADIUS`/`_BIAS`/`_STRENGTH` tuning |
|
||||
| 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 /
|
||||
|
||||
@@ -13,6 +13,8 @@ struct Ao {
|
||||
params: vec4<f32>,
|
||||
// Distance fade start and end in yards, protection luma ramp start and end.
|
||||
fade: vec4<f32>,
|
||||
// x: debug view (0 off, 1 factor, 2 protection, 3 distance/50, 4 raw occlusion); y: gain.
|
||||
debug: vec4<f32>,
|
||||
}
|
||||
|
||||
const SKY_DIST: f32 = 60000.0;
|
||||
@@ -116,7 +118,7 @@ fn ao_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let w = saturate(1.0 - vv / r2);
|
||||
occ += w * max(0.0, dot(v, n) * inverseSqrt(vv + 1e-6) - ao.params.w);
|
||||
}
|
||||
occ = saturate(2.0 * occ / f32(count)) * fade;
|
||||
occ = saturate(ao.debug.y * occ / f32(count)) * fade;
|
||||
return vec4(occ, dist, protect, 1.0);
|
||||
}
|
||||
#endif
|
||||
@@ -148,7 +150,11 @@ fn blur_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
fn apply_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let p = vec2<i32>(frag.xy);
|
||||
let z = depth_at(p);
|
||||
if (is_sky(z)) { return vec4(1.0); }
|
||||
let mode = u32(ao.debug.x);
|
||||
if (is_sky(z)) {
|
||||
// Sky is magenta in the diagnostic views, so "depth reads as sky" is visible.
|
||||
return select(vec4(1.0), vec4(1.0, 0.0, 1.0, 1.0), mode >= 2u);
|
||||
}
|
||||
let dist = -view_at(p, z).z;
|
||||
let size = vec2<i32>(textureDimensions(source));
|
||||
let hc = frag.xy * 0.5 - 0.5;
|
||||
@@ -168,6 +174,10 @@ fn apply_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
}
|
||||
occ /= weight;
|
||||
protect /= weight;
|
||||
if (mode == 2u) { return vec4(protect, protect, protect, 1.0); }
|
||||
// Red = positive view distance, green = negative (a sign error), both / 50 yd.
|
||||
if (mode == 3u) { return vec4(saturate(dist / 50.0), saturate(-dist / 50.0), 0.0, 1.0); }
|
||||
if (mode == 4u) { return vec4(1.0 - occ, 1.0 - occ, 1.0 - occ, 1.0); }
|
||||
let k = 1.0 - ao.params.y * occ * (1.0 - protect);
|
||||
return vec4(k, k, k, 1.0);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
//! blur, then a joint-bilateral upsample MULTIPLIED into the main colour attachment (the MSAA
|
||||
//! attachment when multisampled, so the resolve and every later pass see it). It runs before
|
||||
//! the water depth/colour copy and before transparents, so water, particles and UI are
|
||||
//! untouched and refraction sees the darkened bed. Sky, distant pixels (fade 35-80 yd) and
|
||||
//! bright pixels (lit windows, flames, sunlit sand) are protected.
|
||||
//! untouched and refraction sees the darkened bed. Sky, distant pixels and
|
||||
//! bright pixels (lit windows, flames, sunlit sand) are protected; fade 45-90 yd (Low) / 60-120 yd (High).
|
||||
//!
|
||||
//! cvar `ambientOcclusion`: 0 Off (pass not scheduled, image unchanged), 1 Low, 2 High.
|
||||
//! `WOW_AO=0|1|2` overrides it for the session; `WOW_AO_DEBUG=1` writes the AO term itself.
|
||||
//! `WOW_AO=0|1|2` overrides it for the session; `WOW_AO_DEBUG=1..4` writes a diagnostic view
|
||||
//! (factor, protection, distance, raw occlusion); `WOW_AO_GAIN/RADIUS/BIAS/STRENGTH` tune it.
|
||||
use crate::video::VideoConfig;
|
||||
use benilla_world::{liquid::WaterDepthLabel, view::WorldCamera};
|
||||
use bevy::{
|
||||
@@ -49,21 +50,53 @@ struct AoView {
|
||||
params: Vec4,
|
||||
/// Distance fade start and end (yd), bright-pixel protection ramp (gamma max channel).
|
||||
fade: Vec4,
|
||||
/// x = debug view (0 off, 1 AO factor, 2 protection, 3 distance/50, 4 raw occlusion),
|
||||
/// y = occlusion gain.
|
||||
debug: Vec4,
|
||||
}
|
||||
|
||||
/// Dev tuning knob `WOW_AO_<name>=<f32>`, read once per name; `None` keeps the tier value.
|
||||
fn tuning(name: &'static str) -> Option<f32> {
|
||||
static KNOBS: std::sync::Mutex<Vec<(&'static str, Option<f32>)>> = std::sync::Mutex::new(Vec::new());
|
||||
let mut knobs = KNOBS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some((_, v)) = knobs.iter().find(|(n, _)| *n == name) {
|
||||
return *v;
|
||||
}
|
||||
let v = std::env::var(format!("WOW_AO_{name}"))
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<f32>().ok())
|
||||
.filter(|v| v.is_finite() && *v >= 0.0);
|
||||
knobs.push((name, v));
|
||||
v
|
||||
}
|
||||
|
||||
impl AoView {
|
||||
fn tier(tier: u8) -> Self {
|
||||
let (radius, strength, samples) = if tier >= 2 { (1.5, 0.55, 12.0) } else { (1.2, 0.5, 6.0) };
|
||||
let (radius, strength, samples) = if tier >= 2 { (1.5, 0.7, 12.0) } else { (1.2, 0.65, 6.0) };
|
||||
// Outdoor contacts (house bases, trunks) sit 30-60 yd out; Low stops sooner.
|
||||
let fade = if tier >= 2 { (60.0, 120.0) } else { (45.0, 90.0) };
|
||||
let radius = tuning("RADIUS").unwrap_or(radius);
|
||||
let strength = tuning("STRENGTH").unwrap_or(strength).min(1.0);
|
||||
let bias = tuning("BIAS").unwrap_or(0.05);
|
||||
let gain = tuning("GAIN").unwrap_or(6.0);
|
||||
Self {
|
||||
params: Vec4::new(radius, strength, samples, 0.1),
|
||||
fade: Vec4::new(35.0, 80.0, 0.7, 0.95),
|
||||
params: Vec4::new(radius, strength, samples, bias),
|
||||
fade: Vec4::new(fade.0, fade.1, 0.7, 0.95),
|
||||
debug: Vec4::new(debug_mode() as f32, gain, 0.0, 0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `WOW_AO_DEBUG=1..4`: write a diagnostic term instead of darkening the scene.
|
||||
fn debug_mode() -> u8 {
|
||||
static MODE: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
|
||||
*MODE.get_or_init(|| {
|
||||
std::env::var("WOW_AO_DEBUG").ok().and_then(|v| v.parse().ok()).unwrap_or(0).min(4)
|
||||
})
|
||||
}
|
||||
|
||||
fn debug_view() -> bool {
|
||||
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
*ON.get_or_init(|| std::env::var("WOW_AO_DEBUG").as_deref() == Ok("1"))
|
||||
debug_mode() != 0
|
||||
}
|
||||
|
||||
impl Plugin for AmbientOcclusionPlugin {
|
||||
|
||||
Reference in New Issue
Block a user