Earth
A procedural planet with GPU-baked albedo, night lights and clouds, a lit atmosphere, and an HDR bloom chain tuned so only the sun glows.
// Final grade: bloom add, exposure, vignette, ACES filmic tone mapping, film grain, gamma.
//
// This pass combines bloom, vignette, and film grain. The grain is hashed from
// integer pixel coordinates rather than
// seeded from time, so a given frame is reproducible: the docs thumbnail pipeline
// diffs PNGs and per-frame random noise would never match.
import { pcg2d, unitFloat } from "@vgpu/wgsl-std/hash";
struct Composite {
bloomStrength: f32,
exposure: f32,
vignetteStart: f32,
vignetteDarkness: f32,
grain: f32,
};
@group(0) @binding(0) var beauty: texture_2d<f32>;
@group(0) @binding(1) var bloom: texture_2d<f32>;
@group(0) @binding(2) var samp: sampler;
@group(0) @binding(3) var<uniform> composite: Composite;
struct FragmentIn {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
};
// ACES filmic tone mapping uses Stephen Hill's fit rather than a
// per-channel curve. The input/output transforms are what let a hot orange light
// desaturate toward white instead of clipping to flat yellow.
fn rrtAndOdtFit(value: vec3f) -> vec3f {
let a = value * (value + vec3f(0.0245786)) - vec3f(0.000090537);
let b = value * (vec3f(0.983729) * value + vec3f(0.4329510)) + vec3f(0.238081);
return a / b;
}
fn acesFilmicToneMapping(value: vec3f) -> vec3f {
// Matrix constructors take columns, matching the ACES input and output transforms.
let acesInput = mat3x3f(
vec3f(0.59719, 0.07600, 0.02840),
vec3f(0.35458, 0.90834, 0.13383),
vec3f(0.04823, 0.01566, 0.83777),
);
let acesOutput = mat3x3f(
vec3f(1.60475, -0.10208, -0.00327),
vec3f(-0.53108, 1.10813, -0.07276),
vec3f(-0.07367, -0.00605, 1.07602),
);
// The 0.6 pre-scale preserves the ACES filmic response.
let transformed = acesInput * (value / 0.6);
return acesOutput * rrtAndOdtFit(transformed);
}
@fragment
fn fs_main(input: FragmentIn) -> @location(0) vec4f {
let scene = textureSampleLevel(beauty, samp, input.uv, 0.0).rgb;
let glow = textureSampleLevel(bloom, samp, input.uv, 0.0).rgb;
var color = (scene + glow * composite.bloomStrength) * composite.exposure;
// Vignette: `length(uv - 0.5)` is 0.707 in the corners for any aspect ratio, so
// scaling by 1.6 puts the frame edge near 1 and the ramp stays framing-agnostic.
let falloff = smoothstep(composite.vignetteStart, 1.0, length(input.uv - vec2f(0.5)) * 1.6);
color = color * (1.0 - falloff * composite.vignetteDarkness);
color = acesFilmicToneMapping(color);
var display = pow(clamp(color, vec3f(0.0), vec3f(1.0)), vec3f(1.0 / 2.2));
// Deterministic film grain, hashed from integer pixel coordinates. Applied after
// the transfer curve so it stays a uniform amplitude instead of exploding in the
// shadows the way pre-gamma noise does.
let hashed = pcg2d(vec2u(input.position.xy));
display = display + vec3f((unitFloat(hashed.x) - 0.5) * composite.grain);
return vec4f(clamp(display, vec3f(0.0), vec3f(1.0)), 1.0);
}