| name | threejs-sky-atmosphere-and-haze |
| description | Build sky, atmosphere, and haze in Three.js WebGPU/TSL. Use for authored sky/fog, planetary scattering, depth-aware aerial perspective, or atmosphere-derived sun/sky lighting. |
Sky, Atmosphere, and Haze
Build every branch around one atmosphere model, one scene-linear HDR path, and
one final-output owner.
Process
1. Select the transport claim
Choose the least expensive branch that supports the requested observable:
| Need | Branch | Valid claim |
|---|
| Regional authored sky, distance haze, or height haze | SkyMesh plus TSL fog/haze | Authored appearance in a bounded local frame |
| Real-time planetary sky and aerial perspective | Compact Hillaire-style LUTs | Approximate higher-order transport within measured error |
| Fixed atmosphere with stricter spectral/angular accuracy | Bruneton-style scattering orders or an offline solve | Accuracy demonstrated against the chosen reference |
Use the compact branch for changing cameras and suns. Use the higher-order
branch when its regeneration cost and higher-dimensional products fit the
actual workload. Keep an offline integrator as reference evidence rather than
visible-pixel runtime work.
Complete when: the implementation names one branch, its supported spatial
domain, its physical-versus-authored claim, and the error or visual criterion
that admits it.
2. Establish one model
Declare one length unit for radii, positions, and integration steps; store
extinction/scattering coefficients in its reciprocal unit so
beta * ds is dimensionless. Define the body or local frame, origin policy,
altitude model, sun direction convention, spectral/working basis, and whether
the solar source is normal irradiance or finite-disc radiance.
Initialize WebGPURenderer, await renderer.init(), and confirm the native
WebGPU backend before allocating compute/storage products. Use
NoColorSpace for transport data and keep radiance scene-linear until the
host output stage.
For physically dimensioned extinction, LUT radiometry, mapping equations, phase
conventions, product dependencies, imported LUT/product compatibility, or the
single unit-equivalence fixture, read
references/atmosphere-transport.md.
Complete when: every producer and consumer uses the same frame, length
conversion, sun convention, and radiometric basis; physically dimensioned
extinction also gives the same fixture optical depth on CPU and GPU.
3. Build products in dependency order
The authored local branch samples its sky and distance/height haze directly
from the shared model. It needs host depth only when haze is a separate
depth-aware post. For the compact LUT branch, allocate no product merely
because the branch was selected. Admit products by consumer:
| Product | Allocate only when |
|---|
| Transmittance | an admitted sky, aerial, or lighting path requires it directly or through a dependent product |
| Multiscatter | an admitted sky, aerial, or lighting closure includes higher-order response |
| Irradiance | an admitted diffuse-lighting consumer samples it |
| Sky-view | an admitted visible-sky consumer samples it |
| Aerial RGB inscattering and RGB optical depth | an admitted depth-aware composition consumer samples the paired payload |
A sky-only or lighting-only branch has no aerial allocation or aerial view
dependencies.
Give each product its own dependency key and last-update reason. Atmosphere
profile and body changes dirty base products. Camera body-relative pose,
projection, viewport, and depth mapping dirty aerial products. Camera yaw,
projection jitter, and a pure floating-origin translation leave unchanged
body-frame LUTs valid.
Write 2D products with StorageTexture and textureStore(); write 3D
products with Storage3DTexture and storageTexture3D(). Set formats,
filters, wrapping, and mip ownership explicitly. Treat
renderer.computeAsync() as submission, not GPU completion; synchronize
readback, reuse, and retirement with an actual completion mechanism.
Complete when: every sampled product names all of its physical and view
dependencies, no consumer can observe a newer dependency with an older
dependent product, every unadmitted product has zero allocation and no
dependency state, and unchanged base LUTs survive camera-only changes.
4. Compose the selected branch
For an in-scene authored branch, attach the SkyMesh and TSL fog/haze to the
host scene and keep their output scene-linear.
For a separate depth-aware haze or LUT aerial branch, reuse the host scene
color and depth. Reconstruct the active perspective, reversed, logarithmic, or
orthographic depth convention into a metric segment, then intersect that
segment with the atmosphere. Classify sky through explicit coverage or the
declared clear-depth encoding.
For a visible surface in that branch, apply exactly:
C_out = C_scene * T_segment + S_segment
For a sky pixel, sample sky radiance and the calibrated sun/moon disc. Keep
direct lighting, diffuse sky lighting, cloud shadows, opaque visibility, and
camera-segment transport as separate factors. Let either renderOutput() or
RenderPipeline.outputColorTransform own presentation.
For planetary bodies, exterior cameras, ellipsoids, non-perspective depth, or a
shell/post handoff, read
references/body-depth-and-composition.md.
Complete when: each admitted in-scene branch reaches the host output path
once; each enabled depth-aware branch reconstructs known positions within the
declared tolerance, keeps sky/surface coverage stable, and returns C_scene
under the zero-atmosphere control; tone mapping/output conversion runs once.
5. Expose an admitted lighting handoff
When another system consumes atmosphere-derived lighting, expose only the
quantities it needs:
- sample time, model revision, physics frame/origin, support, filter, age, and
error;
- sample-to-sun unit direction and disc angular radius;
- calibrated solar quantity, unit, and basis;
- either direct sun already attenuated by the atmosphere, or the unattenuated
source plus atmosphere transmittance;
- directional sky radiance, normal-dependent sky irradiance, and whether each
includes the direct disc;
- camera-segment RGB transmittance and RGB inscattering.
Clouds add cloud-only optical depth; geometry adds visibility; water adds its
own path extinction. A consumer chooses one atmosphere direct-light form and
applies each factor once.
Complete when: a visual-only branch has no unused lighting interface; every
admitted lighting consumer can identify quantity, unit, frame, sample age, and
included attenuation, and its factor trace proves that atmosphere, cloud,
geometry, water, and aerial transport each appear at most once.
6. Verify the selected branch
Verify the mechanisms that branch actually uses:
- LUT forward/inverse maps at texel centers, boundaries, horizon split, and
azimuth seam;
- phase normalization and forward-lobe sign;
- optical-depth, radiance, and energy convergence against a higher-accuracy
reference;
- body intersections and every enabled depth encoding;
- product invalidation under parameter, camera, jitter, viewport, and origin
changes;
- fixed-view linear-HDR sky, horizon, night, surface-haze, and exterior-camera
diagnostics;
- create, resize/tier-switch, completion, and disposal ownership.
Complete when: every enabled branch passes its numeric and visual gates,
every diagnostic names the product revision it displays, and repeated
resize/tier-switch cycles leave one live generation per retained product.
Failure signatures
| Symptom | Inspect |
|---|
| Halo appears opposite the sun | phase direction sign |
| Haze changes with world scale | length/coefficient conversion |
| Horizon seam or limb pop | LUT seam, body interval, or owner transition |
| Terrain is darkened twice | direct-light or aerial factor ownership |
| Camera jitter regenerates base LUTs | dependency keys are too broad |
| Off-axis haze is too short | normalized depth was mistaken for ray distance |
Routing boundary
This skill owns molecular/aerosol sky transport, atmosphere-derived sun/sky
lighting, and camera-segment aerial perspective. Use
$threejs-volumetric-clouds for weather-shaped cloud density and cloud-only
shadows, $threejs-image-pipeline for shared scene signals and final-output
ownership, $threejs-exposure-color-grading for metering/tone mapping, and
$threejs-procedural-planets for terrain/body detail.