Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

readme.md

exposure system

Exposure in stops, and eye adaptation — the image settles when the scene gets brighter or darker instead of clipping or going black.

Without it the mapping from light to pixels is fixed. A scene lit for daylight goes black when the sun sets; a scene lit for an interior blows out the moment a door opens. Every other option — brightening the lights, dimming the sky — moves the lighting itself, which changes what the scene is rather than how it is photographed.

Two halves, usable separately:

  • Compensation — a manual offset in stops, the knob for exposing a shot for its shadows or its highlights.
  • Adaptation — the exposure follows the scene on its own, and takes time doing it.

Using it

Exposure is a component — author it once and it persists with the scene:

local id = entity.spawn("exposure").id
entity(id).component.add("AutoExposure", {
    auto = true,          -- follow the scene
    targetGrey = 0.18,    -- photographic middle grey
    meter = "centre",     -- which part of the frame to read
})

Every field is live: edit one in the inspector and exposure follows. With auto off and compensation at 0 the passes are released entirely.

For code that changes exposure at runtime — a cutscene, going underwater, a photo mode — reach for the module directly:

local exposure = require("@builtin::systems.exposure.exposure")

exposure.set({ compensation = -1.5 })  -- any subset; the rest is kept
exposure.tick(dt)                      -- adaptation is a rate
exposure.get()                         -- the settings in force
exposure.active()                      -- are the passes running
exposure.clear()                       -- off, settings kept

tick exists because adaptation moves per second. The component calls it for you; code driving the module directly calls it once a frame.

What the knobs do

A stop is a power of two — the unit compensation, minEV and maxEV are all in. +1 doubles the light reaching the image, +8 multiplies it by 256. The default is the neighbourhood to author in; the accepted range is the width of what the frame can represent, and a value outside it is refused naming the field and the range rather than taken.

fieldeffectdefaultaccepted
compensationManual exposure in stops. +1 doubles the light reaching the image, -1 halves it. Applied on top of any adaptation.0-12..12
autoWhether exposure follows the scene on its own.true—
targetGreyThe luminance adaptation aims to put the metered average on. 0.18 is photographic middle grey.0.180.001..1
minEV / maxEVBounds on how far adaptation may push exposure, in stops — the floor it may stop down to and the ceiling it may open up to. A maxEV of 8 permits 256x of adaptation gain.-4 / 4-12..12
speedUpHow fast exposure RISES, per second, when the scene has gone darker.0.60 or above
speedDownHow fast exposure FALLS, per second, when the scene has gone brighter.2.50 or above
meterWhich part of the frame is read: full / centre / spot."centre"those three
meterRadiusHow wide the metered region is, where 1 reaches the middle of an edge and the corners sit near 1.42, so 2 covers the whole frame.0.60.001..2

minEV and maxEV bound the adaptation alone — how many stops it may move away from the exposure the scene is lit for. A scene stepping between an interior and daylight is served a stop or two either side of the defaults; a ceiling several stops above them hands adaptation a gain large enough to carry the whole frame to white, which is a picture the scene is not lit for rather than a fault in a material or a render pass.

speedUp and speedDown name the direction exposure moves, not the scene. Exposure rises when the scene has gone darker, and that is the slower default on purpose — the eye adapts to darkness far more slowly than to light, which is why walking into a cave should read as a moment rather than a switch.

meter matters more than it looks. The thing a viewer is looking at is rarely the thing filling the most pixels: a bright sky over a dark street will stop the street down until it is unreadable if the whole frame is metered equally.

How it renders

Two compute passes after lighting: a meter that reduces the frame to a 32x32 grid of tile log-luminances, and an adaptation pass that moves the live exposure toward the scene's reading.

Averaging happens in log space, because the eye's response is logarithmic and a handful of very dark pixels would otherwise drag the whole frame's exposure up.

The live exposure lives in a buffer the GPU writes and reads back the next frame. That persistence is the whole mechanism behind an image that settles rather than snaps.

exposure.module reads the adapted stop back each tick and writes it to the chain's display pass as __display__'s exposure property — the same knob a hand-set exposure is.

Files

exposure.package/
  AutoExposure.component/   -- the scene's exposure, authored and persisted
  exposure.module/          -- exposure.set / .tick / .get / .clear / .active
  exposure.renderFeature/   -- the meter and adapt passes