bevy_elements 0.7.5

Living environments for Bevy: sun, moon and stars, climate-driven weather, GPU wind, volumetric clouds, rain, snow and fog, lightning, terrain with hydraulic and thermal erosion, flooding, snow cover, wetness and dust — simulated in compute shaders.
Documentation
// The water's tileable normal map, generated once (docs/research/water-rendering.md, "Generated
// normal map"): 512² `rgba8unorm`, rgb = a tangent-space normal `(−K ∇h, 1)` encoded `n·0.5 + 0.5`,
// a = a tileable fbm for the foam patchiness. Every mip level is the same kernel dispatched with
// the octaves whose wavelength is below the level's two texels left out (a band-limited analytic
// mip: no downsample pass and no energy bleeding across the tile seam).

import bevy_elements::noise::{perlin3_periodic, fbm3_periodic};

struct NormalParams {
    // x: mip level. y: edge of that level in texels. z: octaves of the height kept.
    // w: octaves of the foam fbm kept.
    level: vec4<u32>,
    // x: the slope gain K that makes the rms slope of mip 0 the Cox & Munk value.
    gain: vec4<f32>,
}

@group(0) @binding(0) var<uniform> params: NormalParams;
@group(0) @binding(1) var out_tex: texture_storage_2d<rgba8unorm, write>;

// Texels of the full-resolution map: the height is a function of the texel on this lattice.
const BASE_TEXELS: i32 = 512;
// Height amplitude ratio per octave: 0.5, so that every octave's *slope* (amplitude ×
// frequency) is the same — a flat slope spectrum, as a surface's appearance depends on the slope
// and not on the height (Lagae et al. 2010 §4). The map's total rms slope is unchanged (Cox &
// Munk 1954); only its distribution over the spectrum changes
// (`docs/research/water-surface.md`, Model 1).
const OCTAVE_GAIN: f32 = 0.5;
// Lattice cells of the base octave across the tile: 16, so the map's wavelengths run 0.25 m to
// 6.25 cm (three octaves, the finest at the kernel's four-texel limit). A sheltered body has no
// fetch and no metre-scale wind chop; the metre band is the wave field's (`waves`, fitted to
// Airy's dispersion), and duplicating it here is what smeared the reflection into blobs
// (`water-surface.md`, Model 2).
const BASE_CELLS: f32 = 16.0;
const FOAM_CELLS: f32 = 4.0;
const FOAM_STRETCH: f32 = 2.5;

// Height at integer texel `t` of the full-resolution map, wrapped. Octave `o` has `16·2^o` lattice
// cells across the tile (wavelength `32/2^o` texels) and the same period, so the sum tiles.
fn height(t: vec2<i32>) -> f32 {
    let w = vec2<i32>(t.x & (BASE_TEXELS - 1), t.y & (BASE_TEXELS - 1));
    let u = (vec2<f32>(w) + 0.5) / f32(BASE_TEXELS);
    var h = 0.0;
    var amplitude = 1.0;
    for (var o = 0u; o < params.level.z; o += 1u) {
        let cells = BASE_CELLS * f32(1u << o);
        h += amplitude * perlin3_periodic(vec3<f32>(u * cells, 0.5 + f32(o)), i32(cells));
        amplitude *= OCTAVE_GAIN;
    }
    return h;
}

@compute @workgroup_size(8, 8, 1)
fn water_normals(@builtin(global_invocation_id) id: vec3<u32>) {
    let edge = params.level.y;
    if id.x >= edge || id.y >= edge {
        return;
    }
    // The texel's centre on the full-resolution lattice: level m texel i covers 2^m texels.
    let stride = 512u / edge;
    let centre = vec2<i32>(id.xy * stride) + i32(stride / 2u);
    let hx = height(centre + vec2<i32>(1, 0)) - height(centre - vec2<i32>(1, 0));
    let hy = height(centre + vec2<i32>(0, 1)) - height(centre - vec2<i32>(0, 1));
    let n = normalize(vec3<f32>(-params.gain.x * hx, -params.gain.x * hy, 1.0));
    let u = (vec2<f32>(id.xy) + 0.5) / f32(edge);
    // The periodic fbm is narrow (p10–p90 of 0.41–0.58 once centred), so it is stretched to use
    // the range the patchiness thresholds of `water.wesl` select from.
    let foam = 0.5 + 0.5 * clamp(FOAM_STRETCH * fbm3_periodic(vec3<f32>(u * FOAM_CELLS, 7.3), i32(FOAM_CELLS), params.level.w), -1.0, 1.0);
    textureStore(out_tex, vec2<i32>(id.xy), vec4<f32>(n * 0.5 + 0.5, clamp(foam, 0.0, 1.0)));
}