Weatherglass

  • TypeGPU
  • WebGPU

A weather dashboard whose sky, sun, cloud and rain are computed per pixel on the GPU. The shaders are written as typed TypeScript through TypeGPU rather than as WGSL strings, so the same functions type-check against the buffers that feed them.

View plans

Purchases are not open yet.

Rain without particles

There is no droplet list. Three depth layers each divide the screen into columns — 95 at the front, fewer behind — and the column index is skewed by the wind, so a column is a slanted lane rather than a vertical one. Each column hashes to its own period and firing offset, which means a pixel can work out what rain is in front of it from arithmetic alone.

const columns = 95 - depth * 25;const column = std.floor((uv.x - (uv.y * lean) / aspect) * columns);const seed = hash21(d.vec2f(column, depth * 19.3));const cycle = seconds / (4.8 + seed * 3.7 - depth * 1.25) + seed * 13.7;const progress = std.fract(cycle);const firing = hash21(d.vec2f(column + std.floor(cycle) * 7.1, depth + 3.8));

A droplet is a distance field, turned into the wind

Each drop is a teardrop signed-distance function, not a sprite, so it stays sharp at any size and its edge can be antialiased against the pixel size. Before evaluating it the point is rotated by the wind angle, which means the drops lean with the weather instead of being drawn upright and skewed afterwards.

const angle = std.atan(-lean) - DROP_TILT;const c = std.cos(angle);const s = std.sin(angle);return sdTeardrop(d.vec2f(c * point.x - s * point.y, s * point.x + c * point.y));

Bounding the field before evaluating it

Every pixel would otherwise evaluate every drop in three layers. A cheap rectangle test rejects the ones nowhere near, and the bound it uses is derived rather than guessed: the smooth union lowers the field by at most a quarter of the feather, the bulb and cone fit inside radius 2 at that level, and vertex radii grow by 2.08 per feather unit, so 3 leaves margin.

if (std.abs(point.x) < 2.5 && std.abs(point.y) < 3) {  const distance = windAlignedDrop(point, lean) * radius;// …export function dropCoverageRadius(featherInRadii: number): number {  'use gpu';  return 2 + 3 * featherInRadii;}