Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commitdd348ac6c3

README.md

volumeProbe

Bake a 3D grid of SH L2 irradiance probes and publish it into the renderer's irradiance-volume set: every standard-PBR fragment inside the volume's bounds (movers, avatars, freshly spawned props) takes its ambient term from the field, sampled in fragment via trilinear interpolation across the 8 nearest grid points — no per-receiver setup.

The bake path-traces every probe sample through the scene BVH for up to bounces surface interactions, so probes capture indirect light, color bleed, emissive surfaces, and sky occlusion. Probe storage is a flat array of SH coefficients — 9 vec4 per probe (RGB packed in xyz, padding in w). Re-bake when geometry or lights change.

Every bake persists: the SH brick lands in the scene's baked-lighting container (lightmapData asset) keyed by the volume's fieldId, and the volume's lightmapData ref points at it. A fresh boot restores and republishes the field from the asset (restore, called by the VolumeProbe component's awake) with zero re-baking.

Exports

  • VolumeProbe.bake(volumeEntityId: string, opts: BakeOpts?) -> BakeReport — bake a single probe volume, publish its field, and persist it. Spans many frames by design; raises with the cause when the field cannot be read back (ok is only ever true).
  • VolumeProbe.bakeAll(opts: BakeOpts?) -> { BakeAllEntry } — bake every entity with a VolumeProbe component, sharing scene-geometry + BVH caches, one container and one GPU scheduler across volumes.
  • VolumeProbe.autoPlace(opts?) -> AutoPlaceResult — place volumes over a scope and bake them. A placed volume whose bake raised fails the call, which raises naming the volumes that failed and the first error; no light is marked baked and a held automatic re-bake stays held.
  • VolumeProbe.checkOptions(opts) — raise naming the first bake option the bake cannot honour.
  • VolumeProbe.restore(volumeEntityId: string) -> (boolean, string?) — restore + republish a volume's persisted field (the boot path; no re-bake).
  • VolumeProbe.retire(volumeEntityId: string) — full teardown: retract the published field, destroy the SH buffer, remove the container entry, clear the ref.
  • VolumeProbe.fieldLive(volumeEntityId: string) -> boolean — whether a field is already published under the volume's key this session.
  • VolumeProbe.publishKey(volumeEntityId: string) -> string — the stable key the field publishes and persists under (fieldId, falling back to the entity id).
  • VolumeProbe.findCovering(position: Vec3) -> (string?, boolean) — the volume covering a world position, plus whether the point is contained (false = nearest fallback).
  • VolumeProbe.readIrradiance(volumeEntityId: string) -> { any } — read the baked field back ({ pos, color } per probe).
  • VolumeProbe.bufferName(volumeEntityId: string) -> string — stable per-volume buffer name.

Types:

  • Vec3 = { x: number, y: number, z: number }
  • ColorRGB = { r: number, g: number, b: number }
  • BakeOpts = { samples?, bounces?, intensity?, rayMax?, sky?, baseColor?, rayBudget?, maxFramesInFlight?, bvhCache?, containerCache? }
  • BakeReport = { ok, probeCount, resolution, bounds, ms, bufferName, invalidProbes, gpu }
  • BakeAllEntry = { id: string, report: BakeReport }

Usage

local VolumeProbe = require("@builtin::systems.globalIllumination.volumeProbe")

local report = VolumeProbe.bake(volumeEntityId, { samples = 64 })
print(report.probeCount, "probes baked in", report.ms, "ms")

Notes

  • Volume size + grid resolution come from the VolumeProbe component on the volume entity (sizeX, sizeY, sizeZ, resX, resY, resZ). bake reads them each call so component edits propagate to the next bake without restart.
  • rayMax bounds every path segment; volumes that span large open scenes may want a larger value than the 100m default.
  • bakeAll builds one BVH and shares it across every volume — adding or moving lights between invocations is fine, but adding new meshes requires a fresh bakeAll for the BVH cache to refresh.
  • The GPU work is split by Baking.planChunks into passes of samples over chunks of probes, each inside rayBudget estimated rays (Baking.RAY_BUDGET_PER_FRAME), and issued a frame's worth at a time through a Baking.newGpuScheduler. probe_bake keeps each probe's running sums in its record between passes (stage 1) and a last one-workgroup dispatch (stage 2) writes the completion flag, so a read that sees the flag sees every probe finished. samples and bounces are whole numbers (from 0 and from 1); anything else raises naming the option.