Builtin Library
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
| field | effect |
|---|---|
focusDistance | What the lens resolves, world units. |
focalLength | Millimetres. Longer narrows what stays sharp. |
fStop | Aperture as an f-number. Lower opens up and shortens the depth of field. 0 releases the passes. |
sensorHeight | Millimetres — what converts the lens's units into pixels. 24 is full-frame. |
maxBlur | Ceiling on the blur radius in pixels, which bounds the gather's cost. |
quality | Samples the gather spends. |
nearScale / farScale | Separate 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