Builtin Library
README.md
Volume
Per-entity attachment that renders a 3D voxel texture (cloud, smoke,
nebula, imported VDB grid) into the scene as a volumetric body. The
component does NOT own the volume's lifetime — producers (the
volume module, a custom compute fill, or compute.writeTexture3D)
build the texture and pass its name in volumeName. The entity's
world transform defines the AABB the raymarcher samples.
volumeName is resolved through Volume.get, so a volume the volume
module built is rendered as it stands — at its own dimensions, from its own
density channel, through its occupancy grid, and out of its brick pool if it
is paged. A name no volume answers to is taken as a bare 3D texture some
other system owns, and occupancyName / isRgba describe it.
Usage
local Volume = require("@builtin::systems.volumetrics.volume")
local cloud = Volume.noise({ name = "cloud", size = 64, scale = 4 })
cloud:buildOccupancy({ brickSize = 8 })
local e = entity.spawn("cloud_entity")
e.localScale.set(8, 4, 8)
e.component.add("Volume", {
volumeName = "cloud",
occupancyName = cloud.occupancyName,
density = 6.0,
})
A paged volume costs what it holds and is named the same way:
local cloud = Volume.noise({ name = "cloud", size = 128, threshold = 0.6 })
cloud:buildSparse({ brickSize = 8, threshold = 0.01 })
cloud:releaseDense() -- the brick pool is now the only copy
local e = entity.spawn("cloud_entity")
entity(e.id).localScale = { 8, 4, 8 }
e.component.add("Volume", { volumeName = "cloud", density = 6.0 })
Public fields
| Field | Type | Sync | Default | Meaning |
|---|---|---|---|---|
volumeName | string | Sync | "" | Required. Name of the volume, or of a bare 3D texture, to render. |
occupancyName | string | Sync | "" | R8 occupancy texture for a bare texture's empty-space skipping. Empty = no occupancy. A volume the module built carries its own, and a name set against one is reported as left aside. |
density | number | Sync | 1.0 | Density gain multiplier. |
absorption | number | Sync | 1.0 | Beer-Lambert absorption coefficient. |
emission | number | Sync | 1.0 | Emission gain. |
color | color | Sync | {1,1,1,0} | RGB tint + density floor in alpha. |
stepSize | number | NoSync | 0 | World units per ray step. 0 = auto-derive from volume extent. |
maxSteps | number | NoSync | 256 | Ray-march step cap. |
target | string | Sync | "scene_volume_rt" | Output render target name (composite key). |
targetWidth | number | NoSync | 1280 | Output width (px). |
targetHeight | number | NoSync | 720 | Output height (px). |
useOccupancy | boolean | NoSync | true | Toggle empty-space skipping. |
isRgba | boolean | Sync | true | false reads density from the red channel. Left true, a volume the module built is read from the channel it was built with, and a bare texture as RGBA. |
All public fields use the Field.<kind>(default, Sync|NoSync) schema
(PR #3019). Visual params (look, tint, density) propagate Sync;
quality knobs (stepSize, maxSteps, target resolution, useOccupancy)
are NoSync — each client picks its own perf/quality point.
Lifecycle
awake— no-op. The volume texture is owned by the producer and looked up lazily on the firstupdate.update(dt)— resolvesvolumeNamethroughVolume.get, falling back to a wrapper over a bare texture of that name, and dispatches the compute raymarcher intotarget. The resolution runs every frame, so a volume built, paged or destroyed after this component attached is picked up on the next one.onPropertyChanged(key, value, old)— drops the resolved volume whenvolumeName,occupancyName, orisRgbachanges, forcing a re-resolve on the next frame.
Related
volumemodule — texture builders and name lookup (Volume.noise,Volume.get,Volume.wrap,:buildSparse,:render).vdb_to_zvol.importer— VDB → dense voxel pipeline that produces textures consumable by this component.volumeDemos/volumetricSkypackages — worked examples.