Builtin Library
README.md
exposure
The render feature behind AutoExposure and
@builtin::systems.exposure.exposure. Two compute passes run after lighting:
| order | pass | output |
|---|---|---|
| 200 | exposure_meter | 32x32 grid of tile log-luminances, weighted by the metering mask |
| 202 | exposure_adapt | the live exposure, moved toward the scene's reading |
Metering
exposure_meter gives each tile a slice of the frame and averages the
log-luminance across it, sampling every fourth pixel in each axis — a tile
covers hundreds of pixels and the result is an average, so sampling all of them
buys precision the eye cannot see at sixteen times the cost.
Averaging happens in log space. The eye's response is logarithmic and so is the quantity being averaged, which is why a few very dark pixels do not drag the whole frame's exposure up the way a linear average would.
The metering mask weights that average. Full metering reads everything; centre-weighted tapers to a floor at the frame edge; spot reads only the middle.
Adaptation
exposure_adapt runs as a single invocation, so there is one writer to the
state buffer and no ordering to arrange between workgroups. It reduces the tile
grid, works out the exposure that would land the average on targetGrey, and
moves the live value toward it.
The approach is exponential in dt, so the rate is independent of frame rate.
Rising and falling use different speeds on purpose: the eye adjusts to
brightness far faster than it adjusts to darkness.
The live exposure lives in the exposure_state buffer, written by the GPU and
read back the next frame — that persistence across frames is the whole
mechanism behind an image that settles rather than snaps. The buffer starts
zeroed, and a zero in its "initialised" slot tells the first frame to settle on
its reading immediately instead of adapting up from an exposure the scene never
had.
exposure_state[0] is (currentEV, initialised, targetEV, compensation), and
exposure_adapt also writes the live and target stops to its single-texel
output.
Reaching the picture
exposure.module reads exposure_state back to the CPU each tick, adds
compensation (already held there) to the adapted stop, and writes
exp2(adapted + compensation) to the chain's display pass as __display__'s
exposure property — the one place in the frame a stop turns into a change on
screen.
Parameters arrive through the exposure_params buffer, packed by
@builtin::systems.exposure.exposure, which also supplies the frame's dt.