Exposure And Color Grading

Meter and grade scene-linear Three.js WebGPU images. Use for choosing fixed or automatic exposure; adapting EV on the GPU; assigning tone-map and output conversion; or placing and validating 3D LUTs.

$threejs-exposure-color-grading 1 primary target 2 flagships 1 secondary surface accepted runtime evidence Latest skill update commit 8749183 ↗ 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

Exposure is metered on the GPU: a compute reduction averages log-luminance over the frame (log so that a bright sliver doesn't dominate):

$$\bar L = \exp\!\left(\frac{1}{N}\sum_i \log\big(\epsilon + L_i\big)\right), \qquad L_i = 0.2126R + 0.7152G + 0.0722B$$

Adaptation follows the eye asymmetrically — fast to light, slow to dark — as an exponential approach with split time constants:

$$E_{t+dt} = E_t + (E_{target} - E_t)\,\big(1 - e^{-dt/\tau}\big), \qquad \tau = \begin{cases}\tau_{up} & E_{target} > E_t\\ \tau_{down} & \text{otherwise}\end{cases}$$

Exposure state lives in a storage buffer — no CPU readback stall. One node owns tone mapping and output color transform; grading applies after tone mapping through a 3D LUT: $c' = \operatorname{LUT}_{3D}(\operatorname{tonemap}(E\cdot L))$. Two owners of the output transform is the classic double-transform bug.

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 available0 published images
Same-lab evidenceNo runtime image promotedThe page remains image-free until this skill's own lab supplies hash-bound pixels.

The full skill

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

Exposure And Color Grading

Keep the photographed signal scene-linear until the final image chain. One declared owner controls each exposure group, tone map, and output conversion.

1. Lock the color contract

Name the scene-linear working primaries, radiance scale, alpha convention, and every producer that enters the photographed signal. Convert irradiance through the material/lighting model before metering radiance. Apply one shared physical or perceptual radiance scale to lights, environment, atmosphere, emissive materials, bloom sources, and optical effects.

Partition targets or views into exposure-control groups. A group may share GPU state only when its radiance basis, exposure policy, and reset history are identical; an automatic group also requires the same meter mask, key, and sample schedule. Assign exactly one exposure owner, one tone-map owner, and one output-conversion owner per group.

Complete when: every photographed input has one basis and scale, and every group names its members, exposure/tone-map/output owners, and state-sharing policy.

2. Choose the cheapest meter that meets the image requirement

Choose in dependency order:

  1. fixed EV for a controlled or calibrated view;
  2. a stratified grid or tile sampler for ordinary global auto exposure;
  3. exact full-pixel hierarchical reduction when every pixel or exact mask must contribute;
  4. a log-luminance pyramid only when another feature consumes its levels or spatial statistics;
  5. a histogram only when percentile clipping fixes a demonstrated outlier or bimodal-lighting failure.

Tap resolved, pre-bloom HDR by default. This keeps temporal noise out of the meter and avoids bloom/exposure feedback. A different tap is an authored image policy with a regression fixture.

Read the color-pipeline reference when implementing sampled, exact, pyramid, or histogram metering; it contains the weighted-log equations, traffic model, and small-emitter failure tests.

Complete when: fixed EV names its value/calibration and requires zero meter source reads; otherwise one meter is selected, its source and mask are named, and each cheaper rejected option has a concrete correctness failure.

3. Build the selected exposure controller

For fixed EV, bind the authored or calibrated value directly and allocate no meter, reduction, target-publication, or adaptation state.

For automatic exposure, keep targetEV, currentEV, validity, and frame indices in GPU state. Advance adaptation every rendered frame toward the last valid target, even when the meter runs less often. Keep CPU readback diagnostic-only.

Use this producer schedule only for automatic exposure:

adapt currentEV from the last completed target
  -> render and present with currentEV
  -> reduce the new meter source
  -> publish targetEV for a later frame

Bind the source texture as a real node dependency and expose both the source frame and state frame. Initialize or clear the source before the first reduction. Read GPU exposure state for reduction state, EV adaptation, invalid aggregates, and r185 compute semantics.

Complete when: fixed EV has zero metering/adaptation work, or an automatic frame trace proves which source produced each target, adaptation remains GPU-resident, and an invalid aggregate holds the prior valid target without a CPU substitute.

4. Handle discontinuities before presentation

Give cuts an authored hold, reseed, or fixed-EV policy. Treat a radiance basis, working-primary, quantity, nonlinear-normalization, or exposure-key change as a new exposure epoch. For a pure positive scale change L_new = k * L_old with otherwise identical semantics, preserve the displayed product by shifting the fixed EV, or both automatic states, by -log2(k):

currentEV_new = currentEV_old - log2(k)
targetEV_new  = targetEV_old  - log2(k)

Every other incompatible change starts a new exposure epoch. Automatic exposure resets meter accumulation and reseeds adapted state; fixed exposure rebinds its authored value before the new signal is presented. Resize or DPR changes rebuild only admitted resolution-dependent meter resources and sampling coordinates. Device loss recreates and reseeds only admitted GPU state under the new resource generation.

Complete when: every cut, invalid input, basis/scale change, resize, and device-loss event maps to one conversion, hold, rebuild, or reseed action that finishes before the affected frame is admitted.

5. Compose one final-image chain

Use this domain order unless the LUT declares another complete contract:

scene-linear HDR
  -> exposure
  -> tone map
  -> tone-mapped-linear LUT, when admitted
  -> alpha restoration
  -> output conversion

Unpremultiply before nonlinear RGB operations and premultiply afterward; exposure preserves alpha. A scene-linear LUT needs a declared shaper. A display-encoded LUT owns the exact output primaries and transfer function and therefore sits after renderOutput().

With explicit renderOutput(), set RenderPipeline.outputColorTransform = false. Mark renderPipeline.needsUpdate = true after changing the output node or output ownership. Read tone mapping and LUTs only when loading, authoring, or placing a cube.

Complete when: the graph contains one exposure multiply, one tone map, one working-to-output conversion, and—only when admitted—one LUT placement in its declared domain.

6. Prove the selected branches

Capture deterministic fixtures:

  • a key-gray calibration card with the expected target EV;
  • for automatic exposure, a bright source entering and leaving frame with monotone target/current EV trajectories, plus the selected meter's mask, small-emitter, and cadence cases;
  • when a LUT is admitted, an identity LUT with ramps and saturated swatches in its declared domain;
  • output isolation showing exactly one tone map and one output conversion.

Measure each admitted meter or LUT as a paired graph delta after warmup on the target. GPU time is available only after post-init timestamp-query support is proven; otherwise report the timing as unavailable.

Complete when: all applicable fixtures pass, the final image is inspected, and each failed fixture identifies the meter, adaptation, LUT-domain, alpha, or output-ownership cause.

Routing

Use $threejs-image-pipeline for shared MRT, temporal history, adaptive DPR, and transient lifetime; $threejs-bloom for glare source ownership; and $threejs-visual-validation for fixed-view image evidence.

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.