Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commitdd348ac6c3

readme.md

depthOfField system

Defocus from a lens, not from a blur radius.

Everything rendering equally sharp from the near clip plane to the horizon is one of the plainer tells that an image was computed. A real lens resolves one distance and lets the rest fall away, and how fast it falls away is a property of the lens rather than a number chosen per shot.

Using it

The lens is a component — author it once and it persists:

local id = entity.spawn("lens").id
entity(id).component.add("DepthOfField", {
    focusDistance = 8,     -- world units
    focalLength = 85,      -- millimetres
    fStop = 1.8,           -- aperture; 0 turns defocus off
})

Every field is live. For code that racks focus at runtime — a cutscene, a camera pulling focus onto a subject:

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

depthOfField.set({ focusDistance = 3.5 })   -- any subset; the rest is kept
depthOfField.get()
depthOfField.active()
depthOfField.clear()

What the knobs do

fieldeffect
focusDistanceWhat the lens resolves, world units.
focalLengthMillimetres. Longer narrows what stays sharp.
fStopAperture as an f-number. Lower opens up and shortens the depth of field. 0 releases the passes.
sensorHeightMillimetres — what converts the lens's units into pixels. 24 is full-frame.
maxBlurCeiling on the blur radius in pixels, which bounds the gather's cost.
qualitySamples the gather spends.
nearScale / farScaleSeparate weights for the two fields.

These are a lens's own controls and they interact the way a lens's do: opening the aperture shortens the depth of field, and a longer focal length shortens it further. Both fall out of the optics rather than being dialled in separately.

How much blur a given setting produces depends on how the scene's distances sit relative to focusDistance — a subject at 100 units is barely defocused by a lens focused at 11, and heavily defocused by the same lens focused at 4.

How it renders

bokehBlur.renderFeature runs two compute passes after lighting: one computing the signed circle of confusion from scene depth and the lens, one gathering a disc at that radius.

Each tap contributes only if its own circle of confusion is wide enough to reach the pixel gathering it, which is what stops a sharp foreground bleeding outward over a blurred background.

Files

depthOfField.package/
  DepthOfField.component/    -- the camera's lens, authored and persisted
  depthOfField.module/       -- depthOfField.set / .get / .clear / .active
  bokehBlur.renderFeature/   -- the circle-of-confusion and gather passes