Generated weather-map asset preview

Volumetric Clouds

Implement workload-selected volumetric cloud systems in Three.js r185 with WebGPURenderer, TSL, NodeMaterial, node RenderPipeline passes, compute/storage textures, temporal reprojection, cloud shadows, and error-bounded quality tiers.

$threejs-volumetric-clouds 1 primary implementation 1 flagship 1 secondary surface native evidence pending Latest skill update commit 9077075 ↗ SKILL.md on GitHub ↗ raw (for agents) ↗

Primary implementation surface

These routes are generated from canonical source. Their exact status remains separate from implementation availability.

The approach, mathematically

Clouds are a participating medium raymarched through a weather-shaped density field. Along a primary ray, transmittance obeys the exponential extinction integral:

$$T(s) = \exp\!\left(-\int_0^s \sigma_t\big(\mathbf x(u)\big)\,du\right)$$

Single scattering accumulates in-scattered sunlight attenuated toward the sun at each step, with the Henyey–Greenstein phase function controlling forward silver-lining:

$$L = \int_0^{s_{max}} T(s)\,\sigma_s\,p(\cos\theta)\,L_{sun}(s)\,ds, \qquad p(\cos\theta) = \frac{1-g^2}{4\pi\,(1+g^2-2g\cos\theta)^{3/2}}$$

Temporal reconstruction spreads the march over frames: a sub-pixel jitter sequence plus history reprojection $H_t = \alpha\,C_t + (1-\alpha)\,H_{t-1}(\mathbf{uv} - \Delta_{\mathbf{uv}})$ — the history fetch must be motion-warped, or the amortization degenerates to screen-space smearing.

Preview and evidence ledger

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

Canonical runtime evidence pending4 published images

The full skill

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

Volumetric Clouds

Cloud throughput is won by architecture before code details: march fewer pixels, march only occupied volume, amortize with temporal reprojection, and carry enough depth/velocity data to reject bad history. The taught path is pinned Three.js r185 with WebGPURenderer from three/webgpu, TSL from three/tsl, node materials, compute/storage resources, and a node RenderPipeline.

The density field is an authored meteorological appearance model unless it is coupled to validated atmospheric data. Beer-Lambert attenuation is physical for the declared coefficients; dual-lobe phase fits, octave multiple scattering, powder terms, procedural coverage, and compact shadow tails are approximations. State this boundary in every implementation.

Read references/weather-volume-and-reconstruction.md before implementing or auditing the cloud system.

Shared Environmental And Lighting Boundary

For coupled weather or lighting, first read the router's physics-domain and interaction contract. Consume its versioned PhysicsContext, EnvironmentForcingSnapshot, and LightingTransportSnapshot; do not invent a cloud-only world scale, wind clock, solar basis, or attenuation convention.

The project/environment coordinator is the sole owner and publisher of EnvironmentForcingSnapshot. Clouds consume that immutable boundary and may publish a distinct PrecipitationEmissionSnapshot; they never republish forcing under a cloud-owned revision or clock.

  • Sample environmental air velocity in meters per second from EnvironmentForcingSnapshot.sampleInstant: PhysicsInstant, with its declared frame, altitude/support domain, cadence, interpolation policy, requested/actual oriented spatial footprint, spatial/temporal filter or band, and per-channel error. Every forcing channel has SampledChannel.actualPhysicsTime: PhysicsInstant. Cloud-relative evolution is a separately named velocity relative to the air. It is not water current or vegetation deformation.
  • Select appearance-only or causal-precipitation explicitly. An appearance-only density field may expose an artistic precipitation bias but exposes no physical emission channel. A causal producer publishes dimensioned phase-fraction-resolved liquid/ice PrecipitationEmissionSnapshot with emissionInterval: PhysicsTimeInterval and canonical oriented mass-area flux; every emission channel has SampledChannel.actualPhysicsTime: PhysicsTimeInterval equal to that interval. A volume-source cloud model projects through its support/Jacobian before publication. Include fall-delay or transport model, cadence, uncertainty/error, typed ConservationGroup.boundaryFluxes, a closing ConservationGroup, and an ErrorPropagationLedger for the emitted state version. Delivery accounting is not cloud state: the downstream rain-owned SurfaceExchange.batchLedger owns its immutable InteractionBatchLedger. Any derived SurfaceExchange and InteractionRecord use contained applicationInterval: PhysicsTimeInterval.
  • Consume sun direction and per-channel solar/sky radiance or irradiance quantity, unit, spectral/working basis, filter, error, and atmospheric transmittance from LightingTransportSnapshot.sampleInstant: PhysicsInstant; every lighting channel has SampledChannel.actualPhysicsTime: PhysicsInstant. Apply atmospheric attenuation exactly once using its versioned attenuationFactorIds, not a boolean. Cloud self-shadowing contributes a separate cloud optical-depth/transmittance factor; it never bakes atmospheric attenuation into that factor.
  • Version cloud density, precipitation emission, lighting input, and shadow output independently. The scheduler latches forcing and lighting, evolves a provisional cloud state, derives and validates emission from that state, then commits density and emission generations atomically. It never mutates the consumed forcing snapshot. Downstream rain and surface lighting consume only completed committed publications.
  • Publish committed render-consumed, view-independent cloud state generations through PhysicsPresentationCandidate.presentedStatePairs, with the candidate's requestedPresentationInstant: PhysicsInstant, resourceLeases, and eventSequenceRanges. The candidate contains no camera, render origin, globalToRender, view/projection matrix, shadow/cache epoch, or view-specific current/history/reconstruction state. Every pair carries independent previousPresented.provenance and currentPresented.provenance records of type PresentationSampleProvenance; never collapse them into one shared provenance record. Each arm has its own presentedInstant: PhysicsInstant. The camera owner publishes a per-target/view CameraViewPublication with previousRenderSampleInstant: PhysicsInstant, currentRenderSampleInstant: PhysicsInstant, source-qualified globalToRenderPrevious, globalToRenderCurrent, and view/projection matrices. Visibility, acceleration, cloud-shadow view products, temporal caches, reactive/reset plans, and their lease refs belong to the subsequent per-view ViewPreparationPublication fields visibilityPublicationRefs, accelerationPublicationRefs, shadowViewPublicationRefs, cachePublicationRefs, reactiveEpochs, reactivePublications, resetDependencies, full resourceLeases for newly created camera-dependent generations, and resourceLeaseRefs. The sealed PhysicsPresentationSnapshot contains only snapshotId, candidateId, cameraPublicationId, viewPreparationId, presentationTargetId, viewId, presentedStatePairRefs, resourceLeaseRefs, eventSequenceRanges, and closureManifest, and sealVersion; it copies no pairs or transforms. The candidate exposes separate PresentedStatePair bindings for committed cloud density and, in causal mode, the committed precipitation-emission generation whenever either is presented. closureManifest.exactRequiredLeaseIds and exactEventRangeIds close those bindings and dependencies exactly. Hold, analytic, or no-interpolation policy is explicit in each previous/current provenance record.

Route every physics-authoritative change of cloud equations, state discretization/support, cadence, provider filter, emission representation, or stable identity through the shared QualityTransition. A beauty-march scale, shadow resolution, or reconstruction-only change may remain local only when it does not change any physical state, provider descriptor, emission integral, filter, ID, or cadence.

Keep mobile tiers analytic and sparse: a low-rate authored weather map may sample a coarse wind provider and causal precipitation may use a conservative column flux/fall delay. Do not allocate cloud microphysics or dense transport state unless its observable and error contract requires it.

Phase 1 WebGPU/TSL validation scaffold: examples/webgpu-weather-volume-clouds/. It includes validateCloudConfig(), asset-manifest checks, and shadow/temporal/composite ownership descriptors. Its validator checks contract wiring, not radiometric, spatial, temporal, or visual correctness; use this skill/reference as the implementation specification and the numerical/image gates below before promoting a renderer. Still run node examples/webgpu-weather-volume-clouds/validation.js after scaffold edits.

Legacy WebGL implementation (quarantined, do not extend or use as a pattern): examples/deprecated-weather-volume-clouds/.

Build Order

  1. Start with the cheapest error-valid spatial route: a full-resolution scissored/bounded march for a small projected cloud volume, otherwise a measured reduced-resolution bounded raymarch, blue-noise first-sample offset, transmittance early exit, adaptive step length, cloud shadow map in the same update chain, temporal reprojection with velocity and depth rejection, then depth-aware upsample to full resolution in the node pipeline.
  2. Initialize one WebGPURenderer, call await renderer.init(), and route compute/storage tiers through a capability gate:
await renderer.init();

if (renderer.backend.isWebGPUBackend) {
  // Canonical compute/storage volumetric path.
} else {
  throw new Error("WebGPU backend unavailable for the canonical path.");
}
  1. Use RenderPipeline, pass(), mrt(), PassNode.setResolutionScale(), renderOutput(), and outputColorTransform ownership for the host image chain. Keep one tone-map owner and one output transform owner.
  2. Produce cloud work in TSL Fn().compute(count) dispatches through renderer.compute() or renderer.computeAsync(). Write current cloud radiance/transmittance, representative depth, velocity, history, and compact shadow data into StorageTexture/Storage3DTexture resources with textureStore()/storageTexture(). After initialization use renderer.compute() for submission; r185 computeAsync() is not a GPU-completion fence.
  3. Feed the full-resolution composite as linear HDR cloud radiance plus transmittance into the host RenderPipeline; combine with scene color before the single output transform.

Do not add a second renderer branch to this flagship specification. A missing WebGPU backend is a reported capability failure.

r185 API Verification

Verified against local Three.js REVISION === "185": WebGPURenderer, RenderPipeline, StorageTexture, and Storage3DTexture are exports of three/webgpu; Fn, pass, mrt, renderOutput, storageTexture, storageTexture3D, and textureStore are exports of three/tsl. TRAANode is the default export and traa the named factory from three/addons/tsl/display/TRAANode.js. These symbols are revision-sensitive; smoke-test imports after upgrades. Explicitly configure every storage texture's format/type/filter/mipmap policy; r185 StorageTexture constructor defaults do not imply an HDR cloud target.

Required Architecture

  • Weather-shaped density uses packed 2D weather fields plus 3D base/detail fields. The example's RGBA layout supports up to four layers; it is a storage optimization, not a physical requirement. Keep active layers separate until after per-layer altitude, profile, shape, and detail controls.
  • Ray intervals come from cloud shell bounds and opaque scene depth. Never scale primary cost with camera far distance when the view only crosses a thin cloud layer.
  • Keep the opaque host scene/depth at its required resolution and allocate a separate cloud target when reduction is selected. PassNode.setResolutionScale() scales its entire pass; it does not selectively downsample one material.
  • A small projected cloud bound may use a full-resolution scissor/dispatch and no history when that wins the complete A/B. Larger coverage usually uses a reduced primary march with spatiotemporal blue-noise offset, skips packed empty altitude gaps and conservatively empty 3D macrocells, increases step length only under an error/occupancy gate, and terminates on a bounded remaining contribution.
  • Temporal reconstruction is velocity/depth aware. Same-UV history blending is not accepted under camera or cloud motion. A single representative depth is allowed only for a unimodal contribution distribution; otherwise store depth spread or split layers.
  • Cloud shadows are a separate compact optical-depth product, not a reuse of the beauty march. Update shadows on their own cadence and feed lighting lookups from that representation.
  • Upsampling is depth-aware and edge-aware in the node pipeline, not a blind stretch. TRAANode supplies host temporal AA, not cloud-specific upscaling; cloud reprojection still owns cloud depth, motion, confidence, and topology rejection.

Physical And Numerical Contract

Use scene length in meters or declare an exact conversion. With dimensionless density shape rho, base coefficients beta_s and beta_a in m^-1:

sigma_s = rho * beta_s
sigma_a = rho * beta_a
sigma_t = sigma_s + sigma_a
tau = integral(sigma_t ds)                 // dimensionless
dL/ds = -sigma_t L + j                     // j: radiance per meter
L_acc += T_acc * (j / sigma_t) * (1 - exp(-sigma_t ds))
T_acc *= exp(-sigma_t ds)

Use the j * ds limit as sigma_t -> 0. A direct-light single-scattering source is either sigma_s * T_sun * integral_sunDisk(p * L_sun dOmega) for finite-disk radiance or sigma_s * p * E_sun * T_sun under a declared collimated-irradiance convention. Never substitute radiance for irradiance without the solid-angle integral. Omitting sigma_s or dividing an already- normalized source twice breaks units. Normalize phase functions so 2*pi*integral_-1^1 p(mu)dmu = 1; dual-lobe weights must be nonnegative and sum to one.

Architecture Selection

Evidence Select Gate
Small projected bounded volume, low temporal reuse Full-resolution scissored/bounded march, no history Full-res covered pixels cost less than reduced reconstruction/history and pass error gates
High occupied-sample fraction Bounded adaptive march; no 3D hierarchy Hierarchy lookup/divergence costs more than skipped field reads
Sparse, slowly evolving density Conservative max-density macrocell hierarchy plus DDA Skipped optical-depth/source upper bound is below the radiance error budget
Empty bands only vary by altitude CPU-merged occupied intervals/complementary gaps Debug view proves no occupied band is skipped
High temporal coherence, unimodal depth Full low-resolution grid each frame, jitter, one representative depth/motion Depth spread and rejection rate stay below gates
High coherence, strict dispatch budget Explicit sparse/checkerboard update Missing-sample reconstruction is defined separately from low-resolution jitter
Multi-layer or broad depth contribution Depth moments, front depth, or separate layer histories Single-depth reprojection exceeds disocclusion error gate
Low coherence, topology change, or camera cut Current sample dominates; invalidate history Stale-history confidence is zero
Mobile bandwidth limit Quarter-linear targets, compact formats, fewer live histories, dynamic scale Measured bytes/frame and GPU timestamps fit device/thermal budget

Workload Tuples And Budgets

Authored key Linear scale Primary cap Light cap Shadow product Temporal phase
ultra 1/2 160 8 3x 768-1024 4 frames
high 1/2 96 6 3x 512 4 frames
default 1/4 64 4 2x 384 16 frames
reduced 1/4 32 2 1-2x 128-256, amortized 16 frames

These are workload trial points, not hardware routes or time promises. A number is Derived when it follows from format/resolution, Gated when computed from an error limit, Measured only with hardware/revision/viewport/percentile and thermal state, and otherwise Authored. Keep storage memory explicit: quarter-linear 1920x1080 RGBA16F is 480 * 270 * 8 = 1,036,800 B = 0.989 MiB; half-linear is 960 * 540 * 8 = 4,147,200 B = 3.955 MiB (Derived). A 512x512 RGBA16F cascade is 2 MiB (Derived), but a direct-sun optical-depth cascade should normally be one explicitly formatted channel rather than RGBA16F.

Meter depth cannot be stored directly in binary16 beyond 65,504 m. For a 200 km interval, store interval-normalized/log depth in R16F and decode with the same ray interval, or use R32F; this range constraint is Derived. Record bytes read/written per dispatch, not only allocation size, because mobile cloud passes are commonly bandwidth-limited.

Report whole-frame p50/p95 and paired marginal p50/p95 from the same fixture with the cloud system alternated on/off. Do not subtract unrelated percentile runs, sum pass percentiles, or select a tuple from a device label. Select from measured occupancy, quality gates, bytes/frame, paired marginal cost, and thermal steady state.

Required Controls

  • coverage, cloud type, precipitation, and anvil bias;
  • base/top altitude and vertical density profile per active layer;
  • shape/detail scales, erosion, and height-dependent detail policy;
  • common macro advection plus bounded relative motion for weather, shape, detail, and turbulence fields;
  • density convention, length-unit conversion, beta_s, beta_a, and phase convention;
  • primary step count, adaptive step limits, light step count, and empty-space policy;
  • frame-rate-independent current response time/weight, velocity limit, depth rejection, variance-clipping width, and history reset causes;
  • cloud-shadow extent, cascade count, resolution, update cadence, and compact channel layout;
  • debug mode for each density, march, temporal, and shadow stage.

Color And Output

  • LDR PNG/JPEG color authored in sRGB uses SRGBColorSpace; HDR/generated radiance remains in its declared linear working space. Weather, noise, masks, depth, velocity, LUTs, and shadow optical-depth data use NoColorSpace/linear sampling.
  • HDR cloud current/history/composite buffers use HalfFloatType until the pipeline tone maps.
  • The cloud material/effect must not apply its own output conversion. The host RenderPipeline owns output conversion with outputColorTransform or an explicit renderOutput() node. When renderOutput() is explicit, set renderPipeline.outputColorTransform = false; after switching diagnostic outputNode, set renderPipeline.needsUpdate = true.
  • Generated volume textures use deterministic seeds, documented dimensions, channel semantics, wrap/filter policy, and mip policy. Regenerate them only when their recipe changes.

Failure Conditions

  • density is only procedural noise evaluated at position;
  • the raymarch traverses the full camera range instead of bounded shell/depth intervals;
  • detail noise adds density instead of eroding shaped masses by height;
  • temporal history is accepted without velocity and depth rejection;
  • history is reset on ordinary camera motion instead of reprojected;
  • host surface velocity is used as cloud velocity without reconstructing the advected representative cloud position;
  • shadows use the full beauty march or update every pixel every frame;
  • every layer shares the same wind, altitude profile, and density controls;
  • output is tone mapped or color-converted more than once.
  • meter depth is written to R16F beyond its finite range;
  • averaged mipmaps or nonconservative occupancy masks are used to skip density;
  • low-resolution jitter and sparse/checkerboard missing-sample reconstruction are conflated without a defined target lattice.

Routing Boundary

Use $threejs-choose-skills for preflight when the task spans several rendering systems. Use $threejs-sky-atmosphere-and-haze for molecular/aerosol scattering without weather density, $threejs-image-pipeline for whole-frame HDR/post ownership, $threejs-exposure-color-grading for tone mapping and LUT policy, and $threejs-scalable-real-time-shadows when terrain/scene shadows need CSM or tiled shadow integration. This skill owns weather-shaped cloud volumes, temporal reconstruction, optional typed precipitation emission, cloud lighting, and cloud-only optical-depth shadows. Atmosphere owns the shared lighting transport snapshot; rain owns precipitation transport to receivers.

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.