MONKEY (followups): reduce SSAO grain on alpha-tested foliage
STAGE_AO flags a two-sided depth spike (the smaller one-sided step two pixels out, capped by the second difference: ~0 on planes, grazing planes, silhouettes and contact creases) and stores the smoothness in alpha. STAGE_BLUR turns the spike density of its 4x4 window into a foliage mask, widens the depth tolerance there (up to 11x) and scales the occlusion by 1 - 0.4 * mask. Smooth windows are unchanged. WOW_AO_DEBUG=5 shows the mask. LIGHTING.md documents this and the two earlier followups fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
14578ea26b
commit
f7715c80af
+8
-2
@@ -15,7 +15,7 @@ behind each constant.
|
||||
| Lane | What you see | Main switch |
|
||||
|---|---|---|
|
||||
| Sun shadows, characters | real silhouettes for units instead of the oval blob | `characterShadows` |
|
||||
| Sun shadows, world | trees, buildings and alpha-tested foliage cast; baked terrain shadows step aside | `worldShadows` |
|
||||
| Sun shadows, world | trees, buildings and alpha-tested foliage cast; baked terrain shadows step aside; the static caster cache also refreshes when streamed geometry arrives, so a standing camera picks up late trees (`world_shadow.rs::refresh_streamed_shadows`, independent of volumetric fog) | `worldShadows` |
|
||||
| Dynamic interiors | WMO rooms are lit per fragment by their own fixtures with a soft falloff, gated per room so light does not leak through walls | `interiorLight`, `interiorRoomGate` |
|
||||
| Synthesised lights | torches, braziers, lanterns, campfires and lampposts emit a light in the colour of their flame, with flicker | `fireLightGain`, `fireFlicker` |
|
||||
| Torch shadows | cube-map shadows from point lights onto buildings, models and terrain, with a contact-hardening penumbra and a shadow floor | `interiorShadows`, `exteriorShadows`, `torchShadowStrength`, `interiorShadowSoft` |
|
||||
@@ -32,7 +32,7 @@ behind each constant.
|
||||
| Modern fog | MONKEY (fog): radial distance fog with a gradual exponential curve, a daylight sun lobe, and an end colour shared by the world, WDL hull, sky horizon and volumetric haze. Each zone keeps its authored fog end up to the reference view-distance limit; optional `LightFogBand.dbc` rows provide height, sun and end-fog controls. Interior WMO fog stays classic | Advanced Graphics → Modern Fog, cvar `fogModel` 0 Classic (default) / 1 Modern (High = 1), env `WOW_FOGMODEL=0\|1`; `fog_hook.wgsl`, `lighting/fog_model.rs`, `light/fog_band.rs` |
|
||||
| Rain on surfaces | MONKEY (wet): rain gradually darkens and saturates exposed terrain and models, adds puddles and a sky/sun sheen, and produces rings on exterior Enhanced water. Wetness rises over roughly 90 seconds and dries over roughly four minutes; MONKEY (rainshelter): a camera-centred rain-occlusion map (128² cells of 0.75 yd, filled by vertical collision rays) keeps ground, walls and water under roofs, porches and bridges dry, with a ~1.5 yd drip margin and a soft edge; tree canopies without colliders do not shelter | Advanced Graphics → Wet Surfaces in Rain, cvar `rainSurfaces` 0/1 (default and High 1), env `WOW_RAIN_SURFACES`, `WOW_WETNESS`, `WOW_WET_T`; `weather/wetness.rs`, `weather/shelter.rs`, `wet_hook.wgsl` |
|
||||
| Foliage wind | one weather-fed gust/veer field drives grass and classified tree/bush foliage; grass also parts around the player and nearby units | Advanced Graphics → Foliage Wind, cvar `foliageWind` 0 Off / 1 Grass / 2 Grass + Trees (High = 2), capture override `WOW_FOLIAGE_WIND`; field and benders in `benilla-world/src/wind/`. The wave phase is the CPU-integrated travel (speed integrated over time, wrapped seamlessly), and the waves' spatial term uses the fixed profile heading; the veer turns only the bend. Tree exile copies sway through material marker bit 14 (`FOLIAGE_WIND_MARKER`). Capture offset `WOW_CAPTURE_WIND_T` (s). Known: shadow casters (world-shadow proxies, `torch_depth.wgsl`) do not apply the sway, so leaves sample a static shadow; a future world-camera DepthPrepass must call the same offsets |
|
||||
| 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 |
|
||||
| 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; on alpha-tested foliage (a dense window of two-sided depth spikes) the blur widens its depth tolerance and the AO drops 40 %, which removes the leaf grain while planes, silhouettes and contact creases keep full AO (`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..5` diagnostic views (factor, protection, distance, raw occlusion, foliage mask), `WOW_AO_GAIN`/`_RADIUS`/`_BIAS`/`_STRENGTH` tuning |
|
||||
| Lamp fog | MONKEY (lampfog): the fog fullscreen pass selects the nearest resolved exterior and interior point-table sources and analytically integrates their softened inverse-square light over the camera-to-scene-depth ray. A near-field haze floor makes lamps visible inside the ordinary volumetric haze's 10 yd clear zone; it is zero by day, stronger in rain/fog, and deliberately unshadowed. The table colour already includes intensity, flame flicker and `fireLightGain`. Halo output uses destination headroom and stays below 1.0, so a later bloom pass cannot blow it out | Advanced Graphics → Lamp Fog, cvar `lampFog` 0 Off (default, unchanged) / 1 Low (16 lamps) / 2 High (32 lamps; Graphics preset High = 2), env `WOW_LAMPFOG=0\|1\|2`; `benilla-app/src/volumetric_fog.rs` |
|
||||
| City window daylight | MONKEY (fix-daylight/integration): a district WMO's oversized window batch (e.g. the Cathedral of Light's stained-glass walls) is split into window-sized daylight apertures, so the nave takes daylight pane by pane. Applies to WMOs loaded after a change | Advanced Graphics → City Window Daylight, cvar `daylightWindowSplit` 0/1 (default 1, the merged behaviour); `benilla-world/src/lighting/daylight.rs` (`set_window_split`), bridge `benilla-app/src/dynamic_interior.rs` |
|
||||
| Zone skyboxes | the painted sky a zone's `LightParams` names (clear-weather slot, storm slot lerped by the storm blend) for the living, fading in by the Light sphere falloff with the modern client's crossfade (ghost > WMO > zone); extended `LightSkybox.dbc` flags `0x1` day cycle, `0x2` keep sun/moon/stars/clouds, `0x4` fog-colour horizon cone, plus a celestial second model; every skybox now plays its bones, texture transforms, colour and alpha tracks (`benilla-world/src/skybox.rs`, `skybox_anim.rs`, `benilla-formats/src/light/skybox.rs`) | Advanced Graphics → Zone Skyboxes, cvar `zoneSkyboxes` (0 Off default, 1 On; High = 1), env `WOW_ZONE_SKYBOXES=0\|1` |
|
||||
@@ -77,6 +77,12 @@ column at startup (`Cvars::seed_graphics_preset`) on every row the file does not
|
||||
registered defaults stay their per-lane ones (`farclip` the reference's 350), and captures and
|
||||
tests, which never seed, keep them. The Advanced page's Defaults button lands on High too.
|
||||
|
||||
**Saved presets and new rows** (MONKEY (followups)). On load, `Cvars::reapply_saved_presets`
|
||||
writes the saved `graphicsQuality` rung (and the saved `lightingQuality` rung, else the Graphics
|
||||
rung's) over every governed row the file does NOT carry, so a row registered after the save gets
|
||||
the preset's value, not its registered default. Rows the file carries and session-owned rows stay;
|
||||
`Custom` writes nothing.
|
||||
|
||||
**Render Distance** is `farclip`, the same cvar as the Graphics page's Terrain Distance (which
|
||||
keeps the stock 177–777 slider). Its clamp (`view::FARCLIP_RANGE`) now reaches `FARCLIP_MAX` =
|
||||
1497 yd, the 1.12 grid of 60 carried on. Terrain residency (`terrain_stream/window.rs`), the
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
// STAGE_BLUR half resolution: 4x4 depth-aware box; the 4x4 Bayer rotation cancels inside it.
|
||||
// STAGE_APPLY full resolution: joint-bilateral upsample, output multiplied into the scene.
|
||||
// Stored texel: r = occlusion 0..1 (fade applied), g = view distance (SKY_DIST for sky),
|
||||
// b = bright-pixel protection 0..1.
|
||||
// b = bright-pixel protection 0..1, a (MONKEY (followups)) = surface smoothness 0..1 after
|
||||
// STAGE_AO (0 where both neighbours on an axis jump in depth), 1 - foliage mask after STAGE_BLUR.
|
||||
#import bevy_render::view::View
|
||||
|
||||
struct Ao {
|
||||
@@ -13,7 +14,7 @@ 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.
|
||||
// x: debug view (0 off, 1 factor, 2 protection, 3 distance/50, 4 raw occlusion, 5 smoothness); y: gain.
|
||||
debug: vec4<f32>,
|
||||
}
|
||||
|
||||
@@ -99,6 +100,20 @@ fn ao_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let dy = select(p - pu, pd - p, abs(pd.z - p.z) < abs(p.z - pu.z));
|
||||
var n = normalize(cross(dx, dy));
|
||||
if (dot(n, p) > 0.0) { n = -n; }
|
||||
// MONKEY (followups): cutout leaves scatter depth spikes through a canopy, and there the taps
|
||||
// are noise. The spike is the smaller of the two one-sided steps two pixels out, capped by the
|
||||
// second difference, which is ~0 on any plane (even at grazing angles) and on a silhouette
|
||||
// (one side continuous). Contact creases stay smooth, so contact shadows keep full strength.
|
||||
// The blur turns the spike DENSITY of its window into the foliage mask.
|
||||
let ax = neighbour(f + vec2(2, 0), p).z - p.z;
|
||||
let bx = p.z - neighbour(f - vec2(2, 0), p).z;
|
||||
let ay = neighbour(f + vec2(0, 2), p).z - p.z;
|
||||
let by = p.z - neighbour(f - vec2(0, 2), p).z;
|
||||
let spike = max(
|
||||
min(min(abs(ax), abs(bx)), abs(ax - bx)),
|
||||
min(min(abs(ay), abs(by)), abs(ay - by)),
|
||||
);
|
||||
let smooth_surface = 1.0 - smoothstep(0.01, 0.04, spike / dist);
|
||||
|
||||
let count = u32(ao.params.z);
|
||||
let rot = (bayer4(h) + 0.5) * (6.2831853 / 16.0);
|
||||
@@ -119,7 +134,7 @@ fn ao_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
occ += w * max(0.0, dot(v, n) * inverseSqrt(vv + 1e-6) - ao.params.w);
|
||||
}
|
||||
occ = saturate(ao.debug.y * occ / f32(count)) * fade;
|
||||
return vec4(occ, dist, protect, 1.0);
|
||||
return vec4(occ, dist, protect, smooth_surface);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -130,7 +145,17 @@ fn blur_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let size = vec2<i32>(textureDimensions(raw));
|
||||
let c = textureLoad(raw, h, 0);
|
||||
if (c.g >= SKY_DIST) { return c; }
|
||||
let tolerance = 0.04 * c.g + 0.05;
|
||||
// MONKEY (followups): foliage = spike density over the window. There the depth test would
|
||||
// reject every leaf neighbour and keep the grain, so the tolerance widens and the leftover
|
||||
// occlusion is scaled down; a window of smooth surfaces keeps the tight edge and full AO.
|
||||
var spikes = 0.0;
|
||||
for (var y = -2; y <= 1; y += 1) {
|
||||
for (var x = -2; x <= 1; x += 1) {
|
||||
spikes += 1.0 - textureLoad(raw, clamp(h + vec2(x, y), vec2<i32>(0), size - vec2<i32>(1)), 0).a;
|
||||
}
|
||||
}
|
||||
let foliage = saturate(spikes / 16.0 * 4.0);
|
||||
let tolerance = (0.04 * c.g + 0.05) * (1.0 + 10.0 * foliage);
|
||||
var sum = 0.0;
|
||||
var weight = 0.0;
|
||||
for (var y = -2; y <= 1; y += 1) {
|
||||
@@ -141,7 +166,7 @@ fn blur_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
weight += w;
|
||||
}
|
||||
}
|
||||
return vec4(sum / max(weight, 1e-4), c.g, c.b, 1.0);
|
||||
return vec4(sum / max(weight, 1e-4) * (1.0 - 0.4 * foliage), c.g, c.b, 1.0 - foliage);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -162,6 +187,7 @@ fn apply_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let t = hc - floor(hc);
|
||||
var occ = 0.0;
|
||||
var protect = 0.0;
|
||||
var smooth_surface = 0.0;
|
||||
var weight = 0.0;
|
||||
for (var j = 0; j < 4; j += 1) {
|
||||
let o = vec2(j & 1, j >> 1u);
|
||||
@@ -170,6 +196,7 @@ fn apply_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let w = b.x * b.y / (0.02 + abs(s.g - dist) / dist) + 1e-5;
|
||||
occ += s.r * w;
|
||||
protect += s.b * w;
|
||||
smooth_surface += s.a * w;
|
||||
weight += w;
|
||||
}
|
||||
occ /= weight;
|
||||
@@ -178,6 +205,8 @@ fn apply_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
// 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); }
|
||||
// MONKEY (followups): 5 = foliage mask (black = treated as foliage).
|
||||
if (mode == 5u) { let m = smooth_surface / weight; return vec4(m, m, m, 1.0); }
|
||||
let k = 1.0 - ao.params.y * occ * (1.0 - protect);
|
||||
return vec4(k, k, k, 1.0);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! 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..4` writes a diagnostic view
|
||||
//! `WOW_AO=0|1|2` overrides it for the session; `WOW_AO_DEBUG=1..5` 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};
|
||||
@@ -50,8 +50,8 @@ 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.
|
||||
/// x = debug view (0 off, 1 AO factor, 2 protection, 3 distance/50, 4 raw occlusion,
|
||||
/// 5 surface smoothness, MONKEY (followups)), y = occlusion gain.
|
||||
debug: Vec4,
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ impl AoView {
|
||||
}
|
||||
}
|
||||
|
||||
/// `WOW_AO_DEBUG=1..4`: write a diagnostic term instead of darkening the scene.
|
||||
/// `WOW_AO_DEBUG=1..5`: 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)
|
||||
std::env::var("WOW_AO_DEBUG").ok().and_then(|v| v.parse().ok()).unwrap_or(0).min(5)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user