module environment
Environment / reflection capture — bake the scene into reflection-probe
cube slots from world positions, persist them as `faces6` `.texture` assets,
and set per-probe blend data so surfaces reflect the nearest probe(s). Public
Luau surface over the `__environment` Internal FFI namespace. The generic
"render the scene into a cubemap from a point" capability the reflection
probe system is built on.
global environment
require modules/api/engine/environment
cubeYaml( ) → string
Minimal `cube.yaml` sidecar describing a `faces6` cubemap stored inside a
`.texture` asset. This is the metadata that marks a `.texture` as a cubemap
(vs a flat 2D image). v1 stores PNG faces (no codec for KTX2/HDR yet).
setProbes(probes: { any }) → boolean
Set the active reflection probes' blend data. `probes` is an array of
`{ x, y, z, radius }` (or `{ position = {x,y,z}, radius = r }`); index i is
probe slot i. Surfaces blend the probe slots by proximity to these
positions, gathering the highest `priority` first — each rank takes the
coverage the ranks above it left, so a small interior probe ranked above a
large exterior one wins outright wherever it reaches full weight. Coverage
left over reflects the sky once `captureSky` has run. Queued for next frame.
slot. `priority` defaults to 0.
| arg | type | description |
|---|
| probes | { any } | Array of `{ x, y, z, radius, priority? }`, one per active probe |
examples
environment.setProbes({ { x = 0, y = 2, z = 0, radius = 12 } })captureSky(x: number?, y: number?, z: number?) → boolean
Render the SKY alone into the environment's sky slot from `(x, y, z)` and
arm the sky fallback. A reflective surface no probe covers then reflects the
sky rather than black, and a partially covered one blends the shortfall
against it. The capture holds whatever the scene's sky draws — a gradient, a
physical atmosphere, a skybox material — with no geometry in it, so it stays
correct wherever the camera goes. Once captured, the slot follows the sky
the scene draws: a sky that changes is recaptured from the same position.
Queued — takes effect on the next frame.
atmosphere is sampled at. Defaults to 0.
| arg | type | description |
|---|
| x | number? | World X of the capture position. Defaults to 0. |
| y | number? | World Y of the capture position — the altitude a height-dependent |
| z | number? | World Z of the capture position. Defaults to 0. |
examples
environment.captureSky()
setSkyFallback(active: boolean) → boolean
Arm or disarm the sky fallback against the sky already captured, with no
recapture. Disarmed, reflections come from the probes alone. Arming is
refused while the sky slot holds no capture (`captureSky` fills it), since
an uncaptured slot reflects black; `renderer.reflectionEnvironment()`
reports whether the fallback ended up armed.
| arg | type | description |
|---|
| active | boolean | Whether reflections fall back to the captured sky. |
examples
environment.setSkyFallback(false)
ensureSkyFallback( ) → boolean
Ensure the scene's sky is in the environment's sky slot: a reflective
surface no probe covers then reflects the sky rather than black, and a
partially covered one blends the shortfall against it. Queues a capture
when the sky slot holds none, and re-arms the fallback when a capture is
there but switched off. The engine's own state answers both questions, so
everything that stands a sky up can call this and one capture is shared
between them. Once captured, the slot follows the sky the scene draws on
its own.
examples
environment.ensureSkyFallback()
captureSlot(slot: number, x: number, y: number, z: number) → boolean
Bake the scene into reflection-probe `slot` from `(x, y, z)`.
Renders the FULL scene (geometry + sky) six times from that
point into that slot. Register the probe's position+radius via `setProbes`
so surfaces blend it by proximity. Queued — takes effect next frame.
| arg | type | description |
|---|
| slot | number | Reflection-probe slot (0-based). |
| x | number | World X of the capture position. |
| y | number | World Y of the capture position. |
| z | number | World Z of the capture position. |
examples
environment.captureSlot(0, 0, 2, 0)
captureSlotToAsset(name: string, slot: number, x: number, y: number, z: number, timeoutFrames: number?) →
Bake the scene into reflection-probe `slot` from `(x, y, z)` AND persist
the 6 rendered faces into a `faces6` `.texture` cubemap asset at
`/source/<name>.texture/` (px/nx/py/ny/pz/nz PNGs + a `cube.yaml` sidecar).
Survives an engine restart and syncs like any other texture. Yields a few
frames while the bake + GPU readback complete; must be called from a
task/coroutine context (component hook, `task.spawn`, or `execute`).
past the frames the engine gives a readback before it gives up on one, so
what ends the wait is the readback's own answer).
| arg | type | description |
|---|
| name | string | Destination asset identity (writes `/source/<name>.texture/`). |
| slot | number | Reflection-probe slot (0-based). |
| x | number | World X of the capture position. |
| y | number | World Y of the capture position. |
| z | number | World Z of the capture position. |
| timeoutFrames | number? | Optional max frames to wait for the readback (default 900, |
examples
environment.captureSlotToAsset("probe_lobby", 0, 0, 2, 0)loadSlotFromAsset(name: string, slot: number) →
Load a persisted `faces6` `.texture` cubemap (written by
`captureSlotToAsset`) into reflection-probe `slot` WITHOUT re-rendering the
scene. Reads the 6 face PNGs from `/source/<name>.texture/` and uploads them
into the slot's cube layers. How a persisted probe restores its baked
environment on reload.
| arg | type | description |
|---|
| name | string | Source asset identity (reads `/source/<name>.texture/`). |
| slot | number | Reflection-probe slot (0-based). |
examples
environment.loadSlotFromAsset("probe_lobby", 0)stateFromAsset(identity: string) →
Make a `faces6` `.texture` cubemap the environment that cameras stating
it reflect. A camera rendering an isolated environment reflects only what
it states — never the world's sky or reflection probes — so this is what a
mirror or a pane of glass in its frame shows. The cube is stated under
`identity`, which is the name a camera's `environmentMap` gives it; stating
one already stated, from the same bytes, reads nothing. Called from a task,
it waits for faces whose bytes are still being fetched (the web build
fetches builtin images); called outside one, a face not yet fetched is
answered as missing.
| arg | type | description |
|---|
| identity | string | The cube texture, by the identity `asset.resolve` takes. |
examples
task.spawn(environment.stateFromAsset, "@builtin::textures.studio_environment")
capture(x: number, y: number, z: number) → boolean
Bake the scene into the environment from `(x, y, z)` as the single global
reflection (slot 0 + one full-coverage probe). Every PBR surface reflects it.
Queued — takes effect on the next frame. For multiple proximity-blended
probes use the reflectionProbe system instead.
| arg | type | description |
|---|
| x | number | World X of the capture position. |
| y | number | World Y of the capture position. |
| z | number | World Z of the capture position. |
examples
environment.capture(0, 2, 0)
captureToAsset(name: string, x: number, y: number, z: number) →
Bake the single global reflection AND persist it to a `faces6` `.texture`
asset (slot 0). Yields a few frames; call from a task/coroutine context.
| arg | type | description |
|---|
| name | string | Destination asset identity (writes `/source/<name>.texture/`). |
| x | number | World X of the capture position. |
| y | number | World Y of the capture position. |
| z | number | World Z of the capture position. |
examples
environment.captureToAsset("env_main", 0, 2, 0)loadFromAsset(name: string) →
Load a persisted global reflection asset into slot 0 and make it the
active single reflection (one full-coverage probe).
| arg | type | description |
|---|
| name | string | Source asset identity (reads `/source/<name>.texture/`). |
examples
environment.loadFromAsset("env_main")