Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

exposure

The render feature behind AutoExposure and @builtin::systems.exposure.exposure. Two compute passes run after lighting:

orderpassoutput
200exposure_meter32x32 grid of tile log-luminances, weighted by the metering mask
202exposure_adaptthe 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.