Builtin Library
readme.md
sceneProxy
A coarse volumetric stand-in for the scene's geometry: a grid that answers how far the nearest surface is from any point, cheaply enough to ask along a whole ray.
Asking "what is roughly over there?" otherwise costs what it costs to intersect real triangles, because the triangle set is the only thing a ray can consult. That price is what rules out the techniques that gather light over long distances — they need thousands of cheap, approximate answers, not a few exact ones.
Using it
local sceneProxy = require("@builtin::systems.sceneProxy.sceneProxy")
sceneProxy.build({ resolution = 64 })
sceneProxy.sampleAt({ { x = 0, y = 2, z = 0 } }) --> { { distance, normal } }
sceneProxy.distanceAt({ x = 0, y = 2, z = 0 }) --> world units to the nearest surface
sceneProxy.refresh() -- after something moves
sceneProxy.destroy()
settings() reports the grid, the world extent it covers, and memoryBytes —
fixed by resolution alone, so the cost is known before the build runs.
Both readers answer through proxy_probe, which queries the field with the same
shader module a consuming pass includes, so what a script reads and what a
lighting pass reads in the frame come from one piece of code.
Keeping it current
A field built once describes the scene as it stood. track hands that schedule
to the proxy: it watches the transforms the build would upload and refreshes
when they moved far enough to change a voxel.
sceneProxy.track({ resolution = 64, interval = 0.25, motion = 0.5 })
-- something has to drive the tick each frame:
function update() sceneProxy.maintain() end
SceneProxy.component is that driver for an authored scene — put it on an
entity and the grid is built, followed and released with the scene.
maintain never yields, so a component update can call it: the work it starts
runs as a task of its own. It is cheap on a scene standing still — a slice of a
walk over it and nothing else — and spends at most one refresh per interval
however much moved. motion is the threshold in voxels, measured against where
the landed field put each surface, so a surface drifting below it never
accumulates into a refresh. Geometry that leaves the extent the grid covers
rebuilds it around the scene's new size while refit is on. trackingStats()
reports what the tick looked at, what it cost, and what it has spent.
claim / release are the same thing for something whose presence is the
request: tracking is on while at least one claim stands and the grid goes with
the last of them, so several scene objects asking for a proxy share one grid.
Reading it from a shader
#include "@builtin::systems.sceneProxy.scene_proxy" and bind
sceneProxy.resources.distance as a texture3d. The module fixes no binding
layout — every entry point takes the field as an argument — and it fixes the one
thing a reader and the build have to agree on, which is the grid:
#include "@builtin::systems.sceneProxy.scene_proxy"
let g = spGridFrom(my_params[0], my_params[1]); // sceneProxy.gridParams()
let d = spDistance(sp_field, g, world_pos); // distance to the nearest surface
let n = spNormal(sp_field, g, world_pos, up); // the direction it grows in
let t = spTrace(sp_field, g, origin, dir, 50.0, g.voxel * 1.5, 128);
if spOccluded(sp_field, g, a, b, g.voxel * 1.5, 128) { /* b is not visible from a */ }
sceneProxy.gridParams() packs the two vec4 spGridFrom unpacks, ready to
write into the consuming pass's own parameter buffer.
A ray marches in steps as large as the distance it just read, crossing empty space in a handful of samples rather than testing geometry the whole way.
How it is built
| Pass | What it does |
|---|---|
proxy_voxelize | One thread per scene triangle; marks the voxels its bounding box covers as occupied. Cost follows the scene's surface area, not the grid's volume. |
proxy_seed | An occupied voxel seeds the search with its own coordinate; an empty one starts with no answer. |
proxy_flood | A jump-flood pass. Run with the step halved each time, an answer crosses the whole grid in log2(resolution) passes. |
proxy_resolve | Turns each voxel's nearest-surface coordinate into a world-space distance. |
proxy_probe | One thread per point of a batch a script asked about, answered through the same shader module a consuming pass includes. Pulling the grid itself back would cost res³ floats to learn one number. |
Marking a triangle's bounding box rather than the triangle itself overstates occupancy by at most a voxel in each direction — the direction a proxy should err, since a query is then answered "something is here" slightly too often rather than missing a surface.
Everything is buffer- and texture-bound compute, with no dependency on hardware ray tracing, which is the point: this exists to be affordable where triangle tracing is not.