Files
benilla-octo/crates/benilla/assets/shaders/wdl.wgsl
T
SamandClaude Fable 5 7823f6a47a underwater only when you're in the water, and druid forms work
- A pool belongs to a room now. Uldaman read as deep underwater because a
  mushroom cave's pool 186 yards up claimed every position beneath it forever;
  Undercity's NPCs swam on dry stone because Tirisfal's lake — 95 yards of rock
  overhead — still answered for them, and the camera took the whole screen
  underwater with it. Every liquid query (player, camera eye, every unit) is now
  scoped to the building that owns the water, verified against the real 1.12.1
  files at both reported spots.
- Druid forms work. A live display change — shapeshift, GM morph, revive — was
  only ever read at spawn; now the model swaps instantly and a size change eases
  over the real client's two seconds. A bear-form druid shows no weapon, exactly
  like the original: only a character body attaches held items.
- 126 NPCs across the world were bald and earless. An amulet's display row sits
  in their head slot, and its "tuck the hair under the helm" mask fired with no
  helm to tuck under. A mask now needs an actual helm model; the gnome suppliers
  get their pigtails, ears and earrings back.
- The pouring lava in Blackrock Mountain and the Great Forge came back. A prop
  that spans several rooms (a lava fall is named by up to 19) was culled with
  only the first room that names it, so it blinked in and out by camera angle.
  A prop now draws while any of its rooms is visible.
- The Undercity entrance lightshafts dim to their authored 10% instead of
  flooding the throne room with blinding white columns.
- An indoor pool fogs with its room now, instead of hazing with the outdoor
  scene while the wall beside it hazed with the room. Magma and slime take fog
  too — "fullbright" in the data meant unlit, never unfogged.
- Backing the camera out through the Deadmines entrance swirl no longer blanks
  the whole dungeon: the sealed pocket behind the portal has a detail-only
  floor the room-finder couldn't stand on, so it declared you outside. A third
  fallback leg now names the room from the camera-collision faces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 19:37:17 +07:00

127 lines
7.0 KiB
WebGPU Shading Language
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// WDL distant-terrain shader — the coarse horizon hills the reference draws beyond the streamed
// detailed tiles (docs/knowledge/terrain.md "WDL"). Both stages custom.
//
// The reference draws WDL UNLIT + UNTEXTURED with white vertex diffuse, then fogs it with the SAME
// scene fog as terrain/M2/WMO (VERIFIED apitrace WoW.8: prog 96 vertex-white, fog.color matching the
// zone haze). So the colour is entirely the fog: past `fog_end` it's pure haze, and the visible result
// is fog-coloured hill silhouettes occluding the (un-fogged) sky. Fog math + gamma handling mirror
// terrain.wgsl exactly: planar eye-Z, GL_LINEAR, gamma space; the output is raw gamma — the buffer
// holds bytes and the frame's one decode lives in the FFXGlow combine (GAMMA LANE, 0161).
//
// ## The far band is a BACKDROP, not the far half of a plane partition (decision 0684)
//
// The reference's far-band pass (`CWorldScene` drain `0x6841a0`, wow-re `terrain.md` + `rf35-render-list`)
// does NOT share a clip plane with the detailed world. It draws the coarse hulls under its own
// projection — `SetPerspective(fov, aspect, near = farclip − 33.0, far = horizonfarclip)`, byte-read at
// the two call sites `0x6842f2` (render) / `0x68317f` (the far walk's cull frustum), the `33.0` at
// `[0x8101b0]`, the horizon far from the runtime box `[0xc7b49c]` — into a **compressed depth range
// `[0.955, 0.96]`** (`[0x80febc]=0.96`, `[0x80fec4]=0.005`), i.e. squashed into the back half-percent of
// the depth buffer so it can never occlude anything the detailed world drew. Two properties fall out,
// and BOTH are load-bearing:
//
// * the band **overlaps** the detailed world by 33 yd (≈ one WDL outer cell, spacing 33.333) — so a
// coarse hull that crosses back inside the wall is still drawn, and no hole can open at the seam;
// * it is **depth-pushed**, never near-clipped at the wall — so that overlap cannot poke through the
// fine terrain, and nothing coarse near the camera can paint over the sky (its near plane stops it).
//
// We used to partition instead: terrain discarded past `farclip`, WDL discarded before it, sharing one
// plane with zero overlap. Two different surfaces cannot partition cleanly at one plane — wherever a ray
// grazed the coarse hull entirely INSIDE the wall while the fine mesh it approximates lay beyond it (a
// ridge crest, exactly), both passes discarded and the sky showed through as a band at the horizon
// (the director's "in-between area at the clip barrier", Weazel's Crater, 2026-07-26).
//
// Our depth buffer is Bevy's infinite REVERSE-Z (`perspective_infinite_reverse_rh`: depth = near/eye_z,
// 0 = infinitely far, and never exactly 0 for finite geometry), so the reference's `[0.955, 0.96]`
// window becomes a clamp rather than a `glDepthRange`: every detailed-world fragment lives at
// `depth ≥ near/farclip` (it is discarded past the wall), so clamping WDL just below that value puts the
// whole band behind the world by construction, while leaving it in front of the sky's forced `0.0`
// (`sky_order.rs`, "The depth law"). Order of the three backdrop tiers, all decided by depth and nothing
// else: sky (0.0) → WDL band → detailed world.
#import bevy_pbr::{
mesh_functions,
forward_io::Vertex,
view_transformations::{position_world_to_clip, view_z_to_depth_ndc},
mesh_view_bindings::view,
}
/// How far the far band reaches back INSIDE the detailed world's far-clip wall — the reference's
/// far-band near plane `farclip − 33.0` (`[0x8101b0]`, one WDL outer cell). This overlap is what
/// closes the coarse-vs-fine seam; the depth clamp below is what keeps it harmless.
const WDL_OVERLAP: f32 = 33.0;
/// Nudge off the wall depth so the band is strictly BEHIND a detailed-world fragment sitting exactly
/// at the wall, instead of tying it (reverse-Z `GreaterEqual` lets a tie through, and the winner would
/// then be draw order). Multiplicative so it can never cross zero into the sky.
const WDL_DEPTH_PUSH: f32 = 0.999;
// The shared global light (`lighting::global_light`) — the same buffer terrain, the models and liquid
// bind, mirrored here as the row prefix WDL needs. Rows 4/5 are the SCENE fog (block 1) plus the
// farclip wall, and that is the right block by construction: the band IS the horizon, so it is never
// inside a WMO and never wants the interior block. (WDL used to hold its own copy of these two rows,
// re-pushed per material by `apply_wow_lighting`.)
struct WowLight {
_light_ambient: vec4<f32>, // 0
_light_diffuse: vec4<f32>, // 1
_light_sun: vec4<f32>, // 2
_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 = fog_start yd; y = fog_end yd; z unused; w = farclip wall (0 ⇒ off)
};
@group(#{MATERIAL_BIND_GROUP}) @binding(90) var<storage, read> w: WowLight;
struct WdlVsOut {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_position: vec4<f32>,
}
struct WdlFsOut {
@location(0) color: vec4<f32>,
@builtin(frag_depth) depth: f32,
}
@vertex
fn vertex(in: Vertex) -> WdlVsOut {
var out: WdlVsOut;
let world_from_local = mesh_functions::get_world_from_local(in.instance_index);
out.world_position =
mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(in.position, 1.0));
out.clip_position = position_world_to_clip(out.world_position.xyz);
return out;
}
@fragment
fn fragment(in: WdlVsOut) -> WdlFsOut {
// PLANAR eye-Z (view-space depth), NOT radial — same as terrain.wgsl (apitrace-verified). Used for
// the band's near plane (below) and the fog.
let eye_z = -(view.view_from_world * vec4<f32>(in.world_position.xyz, 1.0)).z;
let farclip = w.fog_params.w;
// The far band's NEAR PLANE: `farclip − 33` (the reference's, see the header). Deliberately NOT the
// wall — the 33 yd overlap is what stops a hole opening at the coarse-vs-fine seam. (0 ⇒ disabled.)
if (farclip > 0.0 && eye_z < farclip - WDL_OVERLAP) {
discard;
}
// White vertex diffuse (the reference's WDL colour); fog does all the colouring. Past `fog_end`
// — and `fog_end = min(zone end, farclip)`, so that is the whole band — this is pure fog colour.
var rgb = vec3<f32>(1.0);
if (w.fog_color.w > 0.5) {
let denom = max(w.fog_params.y - w.fog_params.x, 0.001);
let factor = clamp((w.fog_params.y - eye_z) / denom, 0.0, 1.0);
rgb = mix(w.fog_color.xyz, rgb, factor);
}
var out: WdlFsOut;
// GAMMA LANE (0161): raw gamma out; the frame decodes once in the FFXGlow combine.
out.color = vec4<f32>(rgb, 1.0);
// The depth push (the reference's compressed far-band depth range). `view_z_to_depth_ndc` takes a
// view-space z — negative in front of the camera — so the wall is at `-farclip`.
var depth = in.clip_position.z;
if (farclip > 0.0) {
depth = min(depth, view_z_to_depth_ndc(-farclip) * WDL_DEPTH_PUSH);
}
out.depth = depth;
return out;
}