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 cloud weather map: a camera-centred, 32 km periodic coverage field, thresholded by a
// per-frame histogram so the area fraction of cloud equals `Weather::cloud_cover`.
// See docs/research/clouds.md ("Weather map").
//
//   cloud_histogram_clear   zero the 256 bins
//   cloud_field             histogram of the field, 8 bit
//   cloud_threshold         scan the bins from the top to the quantile 1 − cover
//   cloud_weather           R coverage, G precipitation, B type

import bevy_elements::noise::fbm3_periodic;
import bevy_elements::clouds::common::CloudParams;

@group(0) @binding(0) var<uniform> params: CloudParams;
@group(0) @binding(1) var<storage, read_write> histogram: array<atomic<u32>, 256>;
@group(0) @binding(2) var<storage, read_write> threshold: array<f32, 4>;
@group(0) @binding(3) var weather_out: texture_storage_2d<rgba8unorm, write>;

var<workgroup> local_bins: array<atomic<u32>, 256>;

/// Texels per side of the weather map.
const TEXELS: u32 = 512u;
/// Soft edge of the coverage threshold, field units.
const SOFT_EDGE: f32 = 0.18;
/// Width over which the dome factor (the map's A channel) rises from 0 at the cloud edge to 1,
/// field units.
const DOME_WIDTH: f32 = 0.37;
/// Largest |fbm| of the 3-octave periodic Perlin field on its z slice, measured by
/// `the_noise_normalisation_constants_match_the_noise`; maps the noise onto [0, 1].
const FIELD_MAX: f32 = 0.385;

/// The coverage noise at a texel, [0, 1]: 3 octaves of 2D Perlin with wavelengths 8, 4 and 2 km
/// on a lattice that wraps every 32 km (4 base cells), evaluated at the texel's world position
/// minus the cloud drift. Periodic in the map extent, so its histogram does not depend on where
/// the window sits and the repeat addressing of the map has no seam.
fn coverage_field(texel: vec2<u32>) -> f32 {
    let xz = params.weather.xy + (vec2<f32>(texel) + 0.5) * (params.weather.z / f32(TEXELS));
    let q = (xz - params.drift.xy) / 8000.0;
    let n = fbm3_periodic(vec3<f32>(q.x, q.y, 0.31), 4, 3);
    return clamp(0.5 + 0.5 * n / FIELD_MAX, 0.0, 1.0);
}

@compute @workgroup_size(256)
fn cloud_histogram_clear(@builtin(local_invocation_index) i: u32) {
    atomicStore(&histogram[i], 0u);
}

@compute @workgroup_size(8, 8, 1)
fn cloud_field(
    @builtin(global_invocation_id) id: vec3<u32>,
    @builtin(local_invocation_index) lane: u32,
) {
    if all(id.xy < vec2<u32>(TEXELS)) {
        let bin = min(u32(coverage_field(id.xy) * 256.0), 255u);
        atomicAdd(&local_bins[bin], 1u);
    }
    workgroupBarrier();
    for (var i = lane; i < 256u; i += 64u) {
        let count = atomicLoad(&local_bins[i]);
        if count > 0u {
            atomicAdd(&histogram[i], count);
        }
    }
}

@compute @workgroup_size(1)
fn cloud_threshold() {
    let target_count = params.layer.w * f32(TEXELS * TEXELS);
    var cumulative = 0.0;
    var theta = 256.0;
    for (var k = 255; k >= 0; k -= 1) {
        let count = f32(atomicLoad(&histogram[k]));
        if cumulative + count >= target_count {
            // Bin k spans field values [k, k + 1) / 256; the mass fills it from the top.
            theta = f32(k) + 1.0 - (target_count - cumulative) / max(count, 1.0);
            break;
        }
        cumulative += count;
    }
    threshold[0] = theta;
}

@compute @workgroup_size(8, 8, 1)
fn cloud_weather(@builtin(global_invocation_id) id: vec3<u32>) {
    if any(id.xy >= vec2<u32>(TEXELS)) {
        return;
    }
    let cover = params.layer.w;
    var coverage = clamp(
        0.5 + (coverage_field(id.xy) * 256.0 - threshold[0]) / (SOFT_EDGE * 256.0),
        0.0,
        1.0,
    );
    if cover >= 0.995 {
        coverage = 1.0;
    } else if cover <= 0.005 {
        coverage = 0.0;
    }
    let precipitation = params.weather.w * coverage;
    let dome = clamp((coverage_field(id.xy) * 256.0 - threshold[0]) / (DOME_WIDTH * 256.0), 0.0, 1.0);
    textureStore(
        weather_out,
        vec2<i32>(id.xy),
        vec4<f32>(coverage, precipitation, params.layer.z, dome),
    );
}