threejs-sky-atmosphere-and-haze · git:20260716.2ce13bc · 2026-07-16 · sha256 99bddfca93243aec

threejs-sky-atmosphere-and-haze git:20260716.2ce13bcA

Immutable. This exact content never changes and is served at /api/v1/blob/99bddfca93243aec.

---
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, or the single unit-equivalence fixture, read
[references/atmosphere-transport.md](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 and host depth. The compact LUT branch generates:

```text
transmittance
  -> multiscatter and optional irradiance
  -> camera/sun sky-view
  -> aerial RGB inscattering plus RGB optical depth
  -> scene composition
```

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, and unchanged base LUTs survive camera-only changes.

### 4. Compose sky and aerial perspective

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, apply exactly:

```text
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](references/body-depth-and-composition.md).

**Complete when:** standard and enabled alternate depth modes reconstruct known
positions within the declared tolerance, sky/surface coverage is stable, the
zero-atmosphere control returns `C_scene`, and tone mapping/output conversion
runs once.

### 5. Expose the lighting handoff

Expose only the quantities another system 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:** every lighting consumer can identify quantity, unit, frame,
sample age, and included attenuation, and a 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.