Rain, Snow, and Wet Surfaces

Couple Three.js WebGPU/TSL rain, snow, and wet surfaces. Use for airborne precipitation motion, physical surface deposition, persistent snow or wetness state, weather-driven ripple normals, or impact splashes.

Image: Rain/snow/wet surfaces hardware WebGPU readback. Source lab: webgpu-rain-snow-and-wet-surfaces.

$threejs-rain-snow-and-wet-surfaces 2 primary targets 3 flagships 1 secondary surface accepted runtime evidence Latest skill update commit 2ce13bc ↗ SKILL.md on GitHub ↗ raw (for agents) ↗

Evidence reports

Source hashes, claim verdicts, promoted same-lab media, fixed routes, exact tier contracts, and current limitations.

The approach, mathematically

Weather is one envelope driving many systems: precipitation intensity $\rho_w \in [0,1]$ feeds particles, accumulation, and surface response so they can never disagree. Falling particles integrate gravity with terminal velocity and wind:

$$\mathbf v_{t+dt} = \mathbf v_t + \big(\mathbf g - k\,\mathbf v_t + \mathbf w(t)\big)\,dt$$

Wetness darkens albedo and sharpens specular response — a wet material interpolates:

$$\alpha' = \operatorname{lerp}(\alpha, \alpha_{wet}, w), \qquad c' = c\,(1 - 0.3\,w), \qquad F_0' = \operatorname{lerp}(F_0, 0.02\text{–}0.04, w_{film})$$

Snow accumulates by up-facing exposure $s = \operatorname{smoothstep}(c_0, c_1, \mathbf n\cdot\hat{\mathbf u})\cdot\rho_s$, and puddles fill height-field basins from the bottom up. Ripple normals advance phase per ring: $h(r,t) = A\,e^{-\gamma t}\cos(kr - \omega t)$, baked or generated as normal variants.

Accepted primary labs

Only schema-v2 labs with accepted runtime and evidence contracts appear here. Other source directories remain visible through the demo registry without being promoted to runnable proof.

Preview and evidence ledger

Every image identifies what it proves. Page screenshots demonstrate the published presentation only; generated inputs demonstrate asset channels only; rendering acceptance still requires same-lab readback and a schema-v2 bundle.

Accepted runtime evidence available10 published images
Native WebGPU runtime evidence preview

WebGPU Rain Snow And Wet Surfaces

Accepted
visualCorrectness
PASS
mechanismCorrectness
PASS
performanceCompliance
NOT_CLAIMED
gpuAttribution
NOT_CLAIMED
lifecycleStability
PASS
visualError
PASS
  • Correctness and CDP Chrome hardware physical-route visual review pass on the current source closure.
  • Named-adapter GPU timestamp timing remains NOT_CLAIMED.

The full skill

The complete SKILL.md as loaded by agents, rendered verbatim.

Rain, Snow, and Wet Surfaces

Couple appearance to causes: one time source and wind field drive airborne precipitation, while one owner per receiver integrates deposited rain or snow. Visual particle count samples the weather; it never sets the deposited mass.

Use Three.js r185 WebGPURenderer, TSL, storage nodes, node materials, and the node post stack. Initialize the renderer before allocating compute or storage:

await renderer.init();

if (renderer.backend.isWebGPUBackend !== true) {
  throw new Error('This weather path requires the WebGPU backend.');
}

For cross-system precipitation or receiver state, declare its units, frame, time interval, authority/version, support, validity, and reset semantics; invoke $threejs-choose-skills when ownership spans skills.

Build sequence

1. Name the owners and units

Declare:

  • one monotonically sampled time source and update interval in seconds;
  • one air-velocity field in world metres per second, including its frame, support, cadence, and validity;
  • temperature in kelvin and a named humidity convention when phase, melt, or evaporation depends on them;
  • precipitation forcing as liquid/ice mass-area flux in kg m^-2 s^-1 over a physical receiver support;
  • one receiver-state owner for each liquid or snow inventory;
  • one owner for HDR presentation, tone mapping, and output conversion.

Treat cloud appearance and causal precipitation as separate branches. An appearance-only cloud may coordinate art direction. A causal cloud source publishes a mass flux or airborne inventory with a fall-delay/transport model; rain transports it to receivers on a later ordered stage.

This step is complete when every cause and persistent state has exactly one owner, every exchanged quantity has units and a frame, and every producer is sampled within its stated validity.

2. Select motion before allocating state

Use immutable seeds and analytic vertex motion when position is an exact function of seed, time, and integrated wind. Use recurrent GPU-resident state when turbulence, collisions, feedback, or path history affects the next state. Authored time-varying wind remains analytic only when its displacement integral is available; multiplying the current wind by total elapsed time makes all particles jump when wind changes.

For unbounded visual weather, stream camera-centred cells whose identities, spawn phases, and trajectories are hashed in stable world space. For localized weather, use a world-anchored bounded volume with an intentional boundary. Impacts and accumulation always use world-stable receiver cells independent of the visual pool.

Read precipitation motion when choosing analytic versus recurrent motion, implementing compute updates, generating physical impacts, or budgeting precipitation work.

This step is complete when each requested force or collision maps to an exact analytic term or recurrent state field, and a camera-translation test changes only the visible cell set—not particle phase, impact position, or accumulated receiver state.

3. Separate deposition from visual sampling

Integrate an intensive flux with physical-area quadrature:

sum_i A_i = represented receiver area
deltaMass = deltaTime * sum_i(massFlux_i * A_i)

Each A_i includes the receiver chart Jacobian and has units of square metres. When sparse impacts represent the already-integrated transfer, partition that extensive mass and momentum across impacts so their sum closes the parent transfer exactly once. Keep rendered streak/flake density, sprite size, and visual LOD outside this calculation.

This step is complete when changing visual particle count and visual LOD under the same forcing trace leaves deposited mass and momentum unchanged, and changing receiver cadence leaves the time integral unchanged within the named numerical tolerance.

4. Order whole-grid updates

Use this causal order:

latch time, wind, and precipitation forcing
  -> advance analytic or recurrent airborne precipitation
  -> resolve and bin impacts
  -> publish deposition/impact transfer
  -> integrate receiver liquid and snow state
  -> commit receiver state
  -> build render projection

Split solver, collision/binning, scan/compaction, indirect-argument, and receiver passes into ordered dispatches when later stages consume earlier whole-grid results. A workgroup barrier orders one workgroup; an explicit pass or queue dependency orders the grid. Treat r185 computeAsync() as initialization/enqueue convenience rather than a GPU-completion fence. Keep steady-frame state on the GPU; use host readback only outside frame-critical execution.

This step is complete when every read names the dispatch or committed snapshot that produced it, the receiver integrates after deposition is resolved, and a fixed forcing replay produces the same committed inventory at every supported update cadence.

5. Project one committed receiver state

Read receiver weathering when implementing snow accumulation, wetness, puddles, ripple normals, splashes, or their material response.

Build all surface effects from the committed receiver snapshot:

  • derive snow displacement and snow normals from the same height field;
  • sample object snow in stable model space and gate it with the transformed world-space support normal;
  • apply the early wetness response before enabling heavy-rain ripple normals;
  • use one wetness/puddle mask for roughness, absorption, ripple eligibility, splash intensity, and mask diagnostics;
  • orient splashes from world-space receiver normals and reject candidates that are downward-facing, unsupported, or hidden under the selected visibility policy.

This step is complete when the snow-position and snow-normal diagnostics agree, wetness remains visible with ripples disabled, and transformed or occluded receiver tests accept only supported world-space splash candidates.

6. Present and falsify

Use MeshStandardNodeMaterial or MeshPhysicalNodeMaterial slots for color, roughness, normal, opacity, and displacement. Present through one RenderPipeline. Tag encoded base-color textures as SRGBColorSpace; treat normal, roughness, mask, noise, LUT, ripple, and weather fields as data. Keep HDR buffers linear until the single tone-map/output conversion owner.

Expose diagnostics for forcing revision and age, motion/cells, impacts, deposited mass, receiver terms, snow height/normals, wetness/ripples, splash orientation/visibility, data-texture interpretation, and final output. Measure full-frame and paired weather-on/off CPU/GPU p50/p95, transparent overdraw, hot bytes per frame, active impact tiles, and peak live storage on the named target.

This step is complete when the final and diagnostic views pass the observable checks below, disabling weather restores the baseline image, resize/rebuild preserves owners and resets transient history, and disposal releases every weather-owned buffer, texture, material, and pass.

Observable checks

Observable Failure signature and likely cause
Camera translation preserves precipitation phase and impacts Cell identity or impact support is camera-relative.
Wind changes bend future motion without translating the whole field Current wind was multiplied by elapsed time instead of integrated.
Streak length follows fall speed/exposure, and drift follows the shared wind Sprite geometry or a surface branch uses a separate clock or wind.
Unbounded weather has no emitter seam; localized weather has an intentional edge The selected visual-domain contract is incomplete.
Snow silhouette and lighting move together Displacement and normals come from different fields.
Animated objects keep snow attached to their surfaces Coverage is sampled in world rather than stable model space.
Wet surfaces change before heavy-rain ripples appear Roughness is gated by the ripple mask instead of receiver wetness.
Splashes stay on supported visible faces after transforms Candidate normals or visibility are evaluated in local/unstable space.
Dense recurrent weather has no per-drop object loop or full hot-buffer upload State or presentation left the GPU-resident branch.
Data fields preserve values and final color has one display transform A data texture is decoded as color, or output conversion runs twice.

Routing boundary

Use $threejs-water-optics for bounded water bodies, refraction, caustics, and Beer-Lambert thickness. Use $threejs-particles-trails-and-effects for non-weather particle systems. Use $threejs-dynamic-surface-effects for screen-space touch or clearing histories. Use $threejs-image-pipeline for scene-wide HDR/post ownership and $threejs-scalable-real-time-shadows for large-scene shadow allocation. This skill owns precipitation transport and weather-specific receiver projections; the route-selected receiver owner owns the persistent liquid or snow inventory.

Secondary provider surfaces

Preserved concept proxies and generated-asset previews. They are excluded from primary completion counts and link to the canonical lab through the schema-v2 registry.