Builtin Library
readme.md
weather system
Weather that the world acknowledges.
Rain and snow as particles fall through a scene that never changes, and the eye reads that immediately: particles in front of a dry world. What sells weather is the ground going dark and reflective, snow lying on what faces up, and the patch under a roof staying clear of both.
Using it
Weather is a component — author it once and it persists:
local id = entity.spawn("weather").id
entity(id).component.add("Weather", {
wetness = 0.8,
snow = 0.0,
shelter = 1.0,
})
Every field is live. Both wetness and snow at 0 releases the pass entirely.
For code driving weather over time — a storm cycle, a scripted downpour:
local weather = require("@builtin::systems.weather.weather")
weather.set({ wetness = 0.4 }) -- any subset; the rest is kept
weather.get()
weather.active()
weather.bakeShelter() -- re-read the cover after moving a roof
weather.shelterField() -- where the cover field stands
weather.clear()
What the knobs do
| field | effect |
|---|---|
wetness | How wet surfaces are. Darkens them and adds a sheen that rises toward grazing angles. |
snow | How much snow has settled on upward-facing surfaces. |
shelter | How strictly cover is respected. 1 leaves anything under a roof dry; 0 wets the world evenly. |
shelterDistance | How far up the shelter test looks, world units. |
shelterResolution | Cells across the cover field that test reads, 8-64. More resolves a roof's edge and a narrow overhang. |
shelterRegion | The square of world the cover field covers, { x, z, size }. Omitted, the square already in force is kept; a size of 0 hands the extent back to the scene's own geometry, which is where it starts. |
gloss | How strong the wet sheen is. |
snowBias | How steeply a surface must face up before snow settles. |
wetTint / snowColor / skyColor | What a wet surface darkens toward, the colour of settled snow, and the tint the sky puts on the radiance the surroundings hand a wet surface. |
Rain and snow treat orientation differently on purpose. Rain wets a wall
somewhat; snow either lies on a surface or it does not, so snowBias is a
threshold and wetness is a ramp.
How it renders
surfaceWeather.renderFeature runs one compute pass after lighting. It
reconstructs each pixel's world position and normal, reads how much sky that
point can see out of the cover field, and applies wetness and snow weighted by
that and by orientation.
What the cover field is
Shelter is a fact about the world, so it is answered from the world. The weather
module bakes a cover field over the scene: an XZ grid whose every column holds
the heights of the undersides standing over that place, found by casting a ray
up through the geometry the renderer draws. A point is sheltered when a column
records a surface between just above the point and shelterDistance above it.
A surface therefore reads the same cover from every camera, at every lens, and
whether or not the roof over it is in frame.
The four cells around a point are blended, so cover fades across one cell at a
roof's edge instead of switching on a cell boundary. shelterResolution is what
one cell is worth: the grid spans the scene's geometry, so at 64 cells over a
100 m scene a cell is 1.6 m.
A column was read along a ray cast at its cell's own centre, up to a cell away from the point reading it, and the surface that point stands on has climbed by then if it runs at a slope. The point's normal says how much, so the height its own surface reaches at the cell is what cover has to clear there: a hillside, a ramp and a pitched roof stand open to the sky rather than sheltering themselves. A face steep enough to stand as a wall carries nothing, because its own column records its base below it.
A column holds the eight lowest surfaces standing over its cell, so cover under a ninth is read as open sky.
The field is baked when weather starts, and re-derived whenever the count of
renderables the frame drew moves — which is how geometry that loads after the
weather was set gets its cover. The interval between those checks grows with
what a bake costs, so keeping the field current stays inside a small share of
wall clock however large the scene is. Cover that moves or reshapes without
changing that count is re-read by calling weather.bakeShelter().
Wetness darkens and adds a sheen against water's reflectance — a water film
refracts more light into the surface where it is absorbed, and reflects what
remains in a tighter lobe. What the sheen reflects is the light the surface
stands in, taken the way the surface itself takes it: the radiance the
surroundings hand the whole hemisphere under skyColor's tint, through a
Schlick term that rises toward grazing; plus the sun and every lamp the scene
holds, each through the Cook-Torrance lobe the lit passes shade it with and on
the terms of its own kind — a cone where it points, a rect from the face it
emits along, a distant light along its one direction. So a wet floor in a warm
room reflects that room, a wet floor under a bright sun carries the sun, and a
wet floor with the lights out reflects nothing.
Snow takes over a surface's colour rather than tinting it, modulated by how lit
the surface already was so it does not glow in a dim scene.
Files
weather.package/
Weather.component/ -- the scene's weather, authored and persisted
weather.module/ -- weather.set / .get / .clear / .active
surfaceWeather.renderFeature/ -- the compute pass