At skyQuality >= 1 the dome and cloud materials were rewritten every frame only because `fx.y` carried the sky clock, so each frame re-prepared both material bind groups. The clock now lives in `SkyClockBuffer`, a 16-byte STORAGE | COPY_DST buffer created beside the shared light buffer (AssetSet::Open) and bound read-only by both materials (sky binding 101, cloud binding 103), the same shape as the materials' shared `light_buf`. The render world writes `SkyClock::secs` into it in PrepareResources each frame. `fx.y` is now reserved (0), so `write_gated` only touches a material when a real input moves. The clock is still our own f64 accumulator with the seamless 86400 s wrap (Bevy's globals.time wraps hourly); capture mode still freezes it. A headless build with no RenderDevice gets no buffer and spawns no sky or cloud dome. Verified: cargo check -p benilla; cargo test -p benilla-world --lib (643 passed, 8 ignored); sky.wgsl (Classic and SKY_FX) and cloud.wgsl (plain and SKY_FX_CLOUDS + VERTEX_COLORS) compose and validate under naga_oil 0.20 / naga 27 offline against stubbed bevy_pbr imports and the real fog_hook, monkey_frame and sky_fx modules. No GPU or WoW data here; built on Linux only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
75 lines
3.2 KiB
WebGPU Shading Language
75 lines
3.2 KiB
WebGPU Shading Language
// The cloud dome. Its colour is CPU-side as in the reference: the kernel's `0x6cfb00` port builds
|
||
// the image the reference binds as its texture (`0x58ac70`). This stage applies the dome's rim
|
||
// fade (`0x6d0530`) and blends the raw gamma texels premultiplied. Depth is the far pin in
|
||
// `sky_vertex.wgsl`.
|
||
//
|
||
// MONKEY (sky): at `skyQuality` 2 the texels get GPU detail and a short sun march.
|
||
// Ported from WarcraftXL (https://github.com/WarcraftXL) by iThorgrim — module wxl-retail-clouds, Clouds.cpp.
|
||
|
||
#import bevy_pbr::forward_io::VertexOutput
|
||
#ifdef SKY_FX_CLOUDS
|
||
#import benilla_world::sky_fx
|
||
#endif
|
||
|
||
@group(#{MATERIAL_BIND_GROUP}) @binding(100) var cloud_tex: texture_2d<f32>;
|
||
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var cloud_samp: sampler;
|
||
|
||
// MONKEY (sky): `fx.x` the sky tier, `.y` reserved, `.zw` the unit direction toward the
|
||
// glow body on the sheet (u = world x, v = world z); `lit.rgb` the Light.dbc cloud sun colour
|
||
// (gamma), `.w` the march strength (the body's flatness × the glow envelope).
|
||
struct CloudFx {
|
||
fx: vec4<f32>,
|
||
lit: vec4<f32>,
|
||
};
|
||
@group(#{MATERIAL_BIND_GROUP}) @binding(102) var<uniform> cfx: CloudFx;
|
||
// MONKEY (polish): x = the sky clock (s, wrapped at a day), written per frame outside the material.
|
||
@group(#{MATERIAL_BIND_GROUP}) @binding(103) var<storage, read> sky_clock: vec4<f32>;
|
||
|
||
#ifdef SKY_FX_CLOUDS
|
||
// MONKEY (sky): coverage with detail at a sheet point; `oct` detail octaves.
|
||
fn detailed_cover(uv: vec2<f32>, oct: i32) -> f32 {
|
||
let a = textureSampleLevel(cloud_tex, cloud_samp, uv, 0.0).a;
|
||
if (a <= 0.0) {
|
||
return 0.0;
|
||
}
|
||
return sky_fx::cloud_erode(a, sky_fx::cloud_detail(uv, sky_clock.x, oct));
|
||
}
|
||
#endif
|
||
|
||
@fragment
|
||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||
let texel = textureSample(cloud_tex, cloud_samp, in.uv);
|
||
var a = texel.a;
|
||
var rgb = texel.rgb;
|
||
// MONKEY (sky): High only (the `SKY_FX_CLOUDS` pipeline); Classic and Enhanced keep the
|
||
// kernel's texels as they are.
|
||
#ifdef SKY_FX_CLOUDS
|
||
if (cfx.fx.x >= 1.5) {
|
||
let n = sky_fx::cloud_detail(in.uv, sky_clock.x, 4);
|
||
a = sky_fx::cloud_erode(a, n);
|
||
// A painterly tone inside the mass from the same detail.
|
||
rgb = rgb * (0.93 + 0.14 * n);
|
||
let strength = cfx.lit.w;
|
||
if (a > 0.003 && strength > 0.0) {
|
||
let sd = cfx.fx.zw;
|
||
let stp = 0.011;
|
||
var occl = detailed_cover(in.uv + sd * stp, 2);
|
||
occl += detailed_cover(in.uv + sd * stp * 2.0, 2) * 0.75;
|
||
occl += detailed_cover(in.uv + sd * stp * 3.5, 2) * 0.5;
|
||
let transmit = exp(-occl * 1.44 * strength);
|
||
// WarcraftXL's ambient floor 0.55, softened toward the painted texel.
|
||
let light = 0.62 + 0.38 * transmit;
|
||
let ahead = detailed_cover(in.uv + sd * stp * 1.5, 2);
|
||
let rim = max(ahead - a, 0.0) * (1.0 - a) * transmit * strength;
|
||
rgb = rgb * mix(1.0, light, strength) + cfx.lit.rgb * rim * 0.9;
|
||
rgb = min(rgb, vec3<f32>(1.0));
|
||
}
|
||
}
|
||
#endif
|
||
#ifdef VERTEX_COLORS
|
||
a *= in.color.a; // the dome's rim fade (ring alphas)
|
||
#endif
|
||
// Premultiplied gamma blend; the RGB is already the reference's byte math.
|
||
return vec4<f32>(rgb * a, a);
|
||
}
|