ponko 0.0.1

A fast 2D game engine for simulating millions of things: ECS on all cores, hot reload, an in-game inspector, AI-agent control, and a C++ API.
Documentation
// The sky: a physical atmosphere (Rayleigh and Mie scattering, ozone),
// single scattering, as in sky.rs. Each frame `fs_lut` works it out into
// the sky texture (every direction around the camera); `fs_sky` draws it
// behind everything, with the sun and the stars. Built after mesh.wgsl's
// prelude, for `frame` and the sky texture.

// In kilometers.
const GROUND: f32 = 6360.0;
const TOP: f32 = 6460.0;
const RAYLEIGH: vec3<f32> = vec3<f32>(5.802e-3, 13.558e-3, 33.1e-3);
const RAYLEIGH_HEIGHT: f32 = 8.0;
const MIE_SCATTER: f32 = 3.996e-3;
const MIE_EXTINCTION: f32 = 4.44e-3;
const MIE_HEIGHT: f32 = 1.2;
const OZONE: vec3<f32> = vec3<f32>(0.650e-3, 1.881e-3, 0.085e-3);
// Light scattered more than once, which the model leaves out (SKY_GAIN in sky.rs).
const SKY_GAIN: f32 = 4.0;
const GROUND_ALBEDO: f32 = 0.25;

struct FullScreen {
    @builtin(position) clip: vec4<f32>,
    @location(0) uv: vec2<f32>,
};

// One triangle over the whole screen, at the far plane.
@vertex
fn vs_far(@builtin(vertex_index) i: u32) -> FullScreen {
    let uv = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
    var out: FullScreen;
    out.clip = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 1.0, 1.0);
    out.uv = uv;
    return out;
}

fn extinction(h: f32) -> vec3<f32> {
    let haze = frame.atmosphere.y;
    let rayleigh = RAYLEIGH * exp(-h / RAYLEIGH_HEIGHT);
    let mie = MIE_EXTINCTION * haze * exp(-h / MIE_HEIGHT);
    let ozone = OZONE * max(1.0 - abs(h - 25.0) / 15.0, 0.0);
    return rayleigh + vec3<f32>(mie) + ozone;
}

fn exit_distance(origin: vec3<f32>, dir: vec3<f32>, radius: f32) -> f32 {
    let b = dot(origin, dir);
    let c = dot(origin, origin) - radius * radius;
    return -b + sqrt(max(b * b - c, 0.0));
}

fn hits_ground(origin: vec3<f32>, dir: vec3<f32>) -> bool {
    let b = dot(origin, dir);
    let c = dot(origin, origin) - GROUND * GROUND;
    return b < 0.0 && b * b - c >= 0.0;
}

// The share of sunlight reaching `origin` from `sun`.
fn transmittance(origin: vec3<f32>, sun: vec3<f32>) -> vec3<f32> {
    if hits_ground(origin, sun) {
        return vec3<f32>(0.0);
    }
    let steps = 8;
    let step = exit_distance(origin, sun, TOP) / f32(steps);
    var depth = vec3<f32>(0.0);
    for (var i = 0; i < steps; i++) {
        let p = origin + sun * (step * (f32(i) + 0.5));
        depth += extinction(length(p) - GROUND) * step;
    }
    return exp(-depth);
}

// Sky light from `dir` for a sun of brightness 1 towards `sun`.
fn scattered(dir: vec3<f32>, sun: vec3<f32>) -> vec3<f32> {
    let haze = frame.atmosphere.y;
    let origin = vec3<f32>(0.0, GROUND + max(frame.sky_light.w, 0.002), 0.0);
    var span = exit_distance(origin, dir, TOP);
    if hits_ground(origin, dir) {
        let b = dot(origin, dir);
        span = -b - sqrt(max(b * b - dot(origin, origin) + GROUND * GROUND, 0.0));
    }
    let steps = 24;
    let step = span / f32(steps);
    let mu = dot(dir, sun);
    let rayleigh_phase = 3.0 / (16.0 * PI) * (1.0 + mu * mu);
    let g = 0.8;
    let mie_phase = 3.0 / (8.0 * PI) * ((1.0 - g * g) * (1.0 + mu * mu))
        / ((2.0 + g * g) * pow(1.0 + g * g - 2.0 * g * mu, 1.5));
    var depth = vec3<f32>(0.0);
    var light = vec3<f32>(0.0);
    for (var i = 0; i < steps; i++) {
        let p = origin + dir * (step * (f32(i) + 0.5));
        let h = length(p) - GROUND;
        let ext = extinction(h);
        depth += ext * step * 0.5;
        let scatter = RAYLEIGH * exp(-h / RAYLEIGH_HEIGHT) * rayleigh_phase
            + vec3<f32>(MIE_SCATTER * haze * exp(-h / MIE_HEIGHT) * mie_phase);
        light += scatter * exp(-depth) * transmittance(p, sun) * step;
        depth += ext * step * 0.5;
    }
    if hits_ground(origin, dir) {
        // The ground far away, lit by the sun, seen through the air (and
        // not brightened by SKY_GAIN, which is for the air's light).
        let at = origin + dir * span;
        let up = normalize(at);
        let lit = transmittance(at, sun) * max(dot(up, sun), 0.0) * GROUND_ALBEDO / PI;
        light += lit * exp(-depth) / SKY_GAIN;
    }
    return light;
}

// The sky texture: every direction's light.
@fragment
fn fs_lut(in: FullScreen) -> @location(0) vec4<f32> {
    let dir = sky_dir(in.uv);
    let light = scattered(dir, frame.sun_dir.xyz) * frame.sky_light.rgb * SKY_GAIN;
    return vec4<f32>(light + frame.night.rgb * 0.5, 1.0);
}

// Stars: a few bright points on a grid of directions, fading in as the sun
// goes down.
fn stars(dir: vec3<f32>) -> vec3<f32> {
    let dark = smoothstep(0.05, -0.15, frame.sun_dir.y) * frame.atmosphere.w;
    if dark <= 0.0 || dir.y < 0.0 {
        return vec3<f32>(0.0);
    }
    let p = dir * 300.0;
    let cell = floor(p);
    let h = hash2(cell.xy + cell.z * 17.31);
    if h < 0.996 {
        return vec3<f32>(0.0);
    }
    let center = cell + 0.5 + (vec3<f32>(hash2(cell.yz), hash2(cell.zx), hash2(cell.xy + 3.1)) - 0.5) * 0.6;
    let glow = smoothstep(0.35, 0.0, length(p - center));
    let twinkle = 0.75 + 0.25 * sin(frame.time.x * (2.0 + h * 40.0) + h * 1000.0);
    let tint = mix(vec3<f32>(0.8, 0.85, 1.0), vec3<f32>(1.0, 0.9, 0.75), fract(h * 731.0));
    return tint * glow * twinkle * (h - 0.996) * 1500.0 * dark * smoothstep(0.0, 0.15, dir.y);
}

// The sky behind everything: where nothing was drawn.
@fragment
fn fs_sky(in: FullScreen) -> @location(0) vec4<f32> {
    let far = frame.inv_view_proj * vec4<f32>(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, 1.0, 1.0);
    let dir = normalize(far.xyz / far.w - frame.camera.xyz);
    var light = sky_radiance(dir) + stars(dir);
    // The sun's disk, with a soft edge; bright enough for bloom to catch.
    let sun = dot(dir, frame.sun_dir.xyz);
    let edge = 1.0 - frame.atmosphere.z;
    let disk = smoothstep(frame.atmosphere.z - edge * 0.2, frame.atmosphere.z + edge * 0.2, sun);
    light += frame.sun_color.rgb * disk * 40.0 * step(-0.02, dir.y);
    // Height fog reaches the sky too: all the way to the horizon, and below
    // it. Distance fog doesn't: the sky has no distance.
    return vec4<f32>(fog_along(light, frame.camera.xyz + dir * 20000.0, false), 1.0);
}