# volumetricSky
Orchestration backend for the `VolumetricSky.component`. Owns the
GPU resources the cloud layer needs (cloud volume, raymarch render
target, ping-pong TAA history pair, post-process composite) and
exposes a small public surface the component drives every frame.
## Exports
- `M.spawn(opts?) -> id` — spawn the singleton sky entity, or configure the one already standing, and return its id. Every weather parameter (`coverage`, `sunDir`, `sunColor`, `sunIntensity`, `windSpeed`, …) is settable at the top level or inside a `preset` sub-table; every `VolumetricSky` component field (`shaderVariant`, `cloudSize`, `taaBlendAlpha`, …) by its own name; `name` names the entity. Parameters left out keep their current values. An option that names neither raises, listing both sets, and leaves no entity behind. A weather parameter takes the shape its default carries — a number, or an array of that many numbers — and a value of another shape raises naming the parameter.
- `M.configure(opts) -> boolean` — apply those same weather parameters and component fields to the sky already standing. Returns `false` when none has been spawned.
- `M.defaultPreset() -> table` — a fresh copy of the weather configuration a sky spawned with no options carries. The copy is the caller's to edit.
- `M.destroy() -> ()` — release every resource `spawn` created.
- `M.fillClouds(noiseParams) -> ()` — dispatch the active `cloud_noise_*` shader against the cloud volume. `noiseParams` is the flat table produced by `VolumetricSky.component`'s `snapshotNoiseParams()`.
- `M.dispatchRaymarch(renderParams, viewport) -> ()` — dispatch the active `cloud_raymarch_*` shader, blend with history via the TAA pass, and write into the composite target. `renderParams` is the flat table from `snapshotRenderParams()`.
- `M.find() -> id?` — return the id of the active VolumetricSky entity, or `nil` if none has been spawned by `M.spawn`.
- `M.despawn() -> ()` — despawn the entity `M.spawn` minted (does NOT free GPU resources — call `M.destroy()` for that).
- `M.setCoverage(c) / M.setDensity(g) / M.setSun(dir, color, intensity?) / M.setWind(v)` — convenience setters that route through the active component's `public` fields.
- `M.reprojectionDepthNdc(camPos, fwd, aabbMin, aabbMax, near, far) -> number` — the NDC z the TAA pass carries history through, taken from where the cloud stands along the centre view ray. A point at range `d` travels `translation / d` across the screen, so this is what keeps a translating camera's history landing on the part of the picture the cloud moved to.
## Usage
```luau
-- The component drives this module automatically. Direct M.* use is
-- rare; the canonical entry point is attaching VolumetricSky to an
-- entity (or loading a preset and passing it to component.add):
local sky = entity.spawn("sky")
sky.component.add("VolumetricSky", {})
-- Driving the layer by hand instead, a frame at a time. Each dispatch
-- creates the resources it needs at the size its own arguments name —
-- the cloud volume from `noiseParams.cloudSize / cloudHeight`, the
-- render target and composite from the viewport:
local Sky = require("@builtin::systems.volumetrics.volumetricSky")
Sky.fillClouds(noiseParams)
Sky.dispatchRaymarch(renderParams, { width = 1280, height = 720 })
```
## Notes
- `shaderVariant` selects which compute shader pair (`cloud_noise_v3 +
cloud_raymarch_v3` / `cloud_noise + cloud_raymarch_v4` /
`cloud_noise_v5 + cloud_raymarch_v5`). The module resolves the
variant strings to absolute identities — relative paths don't work
from a module body (the chunkname retains an `.init` segment that
throws off the rsplit-to-parent step in `resolve_relative_dot`).
- The cloud volume + history pair are RGBA16F — 256×128×256 ≈ 32 MiB
VRAM. Drop to 192/96 via `cloudSize` / `cloudHeight` if tight.
- The TAA pass is a single compute dispatch blending current sample
with the previous frame's RT through the history pair. Set
`taaBlendAlpha = 1.0` to disable while still going through the same
code path (useful for benchmarking).
- All public setters write through to `VolumetricSky.component`'s
`public` fields, which means they replicate as Sync if the
component is sync-enabled. Tweaks survive scene reloads as long as
the same entity is alive.
- `SkyPreset` and its defaults are declared here, and the component
declares its `preset` field from them. `M.spawn` / `M.configure` read
that same key set to tell a weather parameter from a component field, so
a parameter added to the preset reaches the schema and the option routing
in one edit.
# volumetrics
Volumetric (3D-texture) rendering, end to end, in one package. Built
entirely on the engine's generic `compute.*` GPU primitives — no
Rust changes are needed to author new volumetric content.
## Layout
| Asset | Identity | Role |
|---|---|---|
| `Volume.component` | `@builtin::systems.volumetrics.Volume` | Attach a 3D voxel texture to an entity; raymarches it through the entity's transform AABB. |
| `VolumetricSky.component` | `@builtin::systems.volumetrics.VolumetricSky` | Singleton procedural cloud layer with TAA. |
| `ParticipatingMedium.component` | `@builtin::systems.volumetrics.ParticipatingMedium` | Mark an entity as fog/smoke/dust that attenuates the light passing through it. |
| `mediaTransmittance.module` | `@builtin::systems.volumetrics.mediaTransmittance` | Light-space optical-depth map: the registry, the settings, and the basis the map is built in. |
| `mediaShadow.renderFeature` | `@builtin::systems.volumetrics.mediaShadow` | Builds the optical-depth map and attenuates each surface by the density in front of it. |
| `volume.module` | `@builtin::systems.volumetrics.volume` | Builders (`noise`/`sphere`/`box`/`wrap`) + the `.zvol` header reader + occupancy + brick paging + the per-entity raymarch dispatch. |
| `VolumeSequence.component` | `@builtin::systems.volumetrics.VolumeSequence` | Play an open volume sequence through an entity's transform AABB. |
| `volumeSequence.module` | `@builtin::systems.volumetrics.volumeSequence` | A run of `.zvol` frames streamed through a fixed ring of resident volumes. |
| `volumeDemos.module` | `@builtin::systems.volumetrics.volumeDemos` | Shared pipeline for the showcase scenes (fill + emissive raymarch + composite). |
| `volumetricSky.module` | `@builtin::systems.volumetrics.volumetricSky` | Cloud volume + raymarch + TAA + composite orchestration. |
| `shaders/*.shader` | `@builtin::systems.volumetrics.shaders.*` | `volume_raymarch`, `volume_pages`, `volume_pack`, `cloud_noise[_v3/_v5]`, `cloud_raymarch[_v3/_v4/_v5]`, `cloud_taa`. |
| `importers/vdb_to_zvol.importer` | (auto-discovered) | `.vdb` → dense `.zvol` via the `openvdb` WASM plugin (procedural fallback when the plugin isn't loaded). |
| `presets/cumulus_default.preset` | `@builtin::systems.volumetrics.cumulus_default` | Canonical full `VolumetricSky` weather preset. |
| `demo/*.scene` | `@builtin::systems.volumetrics.demo.*` | `sdf_solids`, `smoke_plume`, `nebula`, `godrays`, `zvol_viewer`, `sky`. |
## Path convention — fully-qualified builtin identities
Every intra-package reference uses the asset's fully-qualified builtin
identity, the same convention every other builtin package follows
(cf. `particles.package`):
```luau
local Volume = require("@builtin::systems.volumetrics.volume")
local Sky = require("@builtin::systems.volumetrics.volumetricSky")
local V = require("@builtin::systems.volumetrics.volumeDemos")
-- shader identities, e.g. asset.resolve("@builtin::systems.volumetrics.shaders.volume_raymarch")
```
Qualified identities are caller-independent: they resolve identically
from a module, a scene entrypoint, or a component. Caller-relative
(`.sibling`) requires are deliberately avoided here because the
component runtime caller identity is `@builtin::components.<Name>`, so a
`.sibling` require from a component resolves against the wrong root.
(An earlier revision of this package used a `~volumetrics` package-root
alias for relocatability; that alias is not resolvable on current main,
so the package uses qualified identities throughout.)
## Usage
```luau
-- Per-entity volume. The component resolves the name through `Volume.get`,
-- so a volume paged into a brick pool renders from the pool under the same
-- name:
local Volume = require("@builtin::systems.volumetrics.volume")
local cloud = Volume.noise({ name = "cloud", size = 64, scale = 4 })
cloud:buildSparse({ brickSize = 8 })
cloud:releaseDense()
entity(id).component.add("Volume", { volumeName = "cloud", density = 6.0 })
-- Procedural sky:
local sky = entity.spawn("sky")
sky.component.add("VolumetricSky", {})
preset.apply("cumulus_default", { entity = sky.id, component = "VolumetricSky" })
-- VDB import: drop a `.vdb` into the VFS; the importer writes a `.zvol`
-- alongside it. Load it with the zvol_viewer demo scene.
-- Animated run: one `.zvol` per frame, played through a bounded ring.
local Seq = require("@builtin::systems.volumetrics.volumeSequence")
Seq.open({ name = "plume", frames = framePaths, resident = 3, fps = 24 })
entity(id).component.add("VolumeSequence", { sequenceName = "plume", density = 6.0 })
```
## Plugin dependency
The VDB importer delegates real OpenVDB parsing to the `openvdb` plugin
in the builtin library, `@builtin::plugins.openvdb`. Without the plugin
the importer falls back to a deterministic procedural torus so the
pipeline stays functional.
·computeshader · born here
❒asset # cloud_raymarch_v5
Production volumetric-cloud raymarcher, v5 (Nubis/Hillaire style): Hillaire
multi-scatter octaves (fixes black cloud cores), energy-conserving Beer-Powder-MS
lit-edge glow, a 6-offset Wrenninge cone-trace shadow, and a dual-lobe
Henyey-Greenstein phase. Reads the cloud volume + occupancy textures and writes the
lit raymarch result.
Bindings (see bindings.yaml): `clouds` (texture3d), `occupancy` (texture3d),
`cloud_smp` (sampler), `out_tex` (storage2d rgba16f), `U` (read `array<f32>`
uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_raymarch_v5", ...)` (textures/sampler/storage texture require resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_noise
Procedural volumetric-cloud noise fill (Schneider/Hillaire style). A low-frequency
FBM gives the base blob silhouette, a high-frequency FBM erodes it near the surface
for the fluffy cumulus look, plus altitude shaping (round bottom, full middle, anvil
top). Writes the 3D cloud density texture.
Bindings (see bindings.yaml): `clouds` (storage3d rgba16f — the cloud volume), `U`
(read `array<f32>` uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_noise", ...)` (storage texture requires resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_raymarch
Volumetric-sky cloud raymarcher with Frostbite-style lighting: Wrenninge/Hillaire
multi-scatter approximation (3 octaves), a 5-sample cone-traced soft self-shadow,
and combined Beer-Lambert + Powder so cloud edges glow toward the sun. Reads the
cloud volume + occupancy textures and writes the lit raymarch result.
Bindings (see bindings.yaml): `clouds` (texture3d), `occupancy` (texture3d),
`cloud_smp` (sampler), `out_tex` (storage2d rgba16f), `U` (read `array<f32>`
uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_raymarch", ...)` (textures/sampler/storage texture require resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_noise_v5
Volumetric-cloud noise generator, v5 (Nubis/Schneider style). Builds two
foundational noises — Perlin-Worley FBM for the lumpy "cauliflower" base shape and
Worley FBM for high-frequency detail erosion — under weather-map control. Writes
the 3D cloud density texture.
Bindings (see bindings.yaml): `clouds` (storage3d rgba16f — the cloud volume), `U`
(read `array<f32>` uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_noise_v5", ...)` (storage texture requires resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_taa
Cloud TAA composite — temporal anti-aliasing for the raymarch output. Reprojects
last frame's blended output through the previous view-projection, color-clamps the
history to the current sample's neighborhood to suppress ghosting, and exponentially
blends the two. Writes both the next-frame history slot and the externally-sampled
`scene_volumetric_sky` texture the composite path reads.
Reprojection runs through the world point named by uniform `[37]`, the NDC z of the
range the cloud stands at along the view ray — `volumetricSky.reprojectionDepthNdc`
computes it. The weight the history carries falls to zero across the last
`REPROJECT_BORDER_TEXELS` of the reprojected frame, so a pixel whose history lies
off the previous frame reaches the raymarch's own output down a gradient rather
than across a line.
Bindings (see bindings.yaml — slot order must match the Luau dispatch): `cur_tex`
(texture2d), `hist_prev` (texture2d), `smp` (sampler), `out_hist` (storage2d
rgba16f), `out_scene` (storage2d rgba16f), `U` (read `array<f32>` uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_taa", ...)` (textures/sampler/storage textures require resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_raymarch_v3
Volumetric-sky cloud raymarcher (Frostbite-style lighting), v3 variant:
Wrenninge/Hillaire multi-scatter (3 octaves), 5-sample cone-traced soft shadow, and
combined Beer-Lambert + Powder edge glow. Reads the cloud volume + occupancy
textures and writes the lit raymarch result.
Bindings (see bindings.yaml): `clouds` (texture3d), `occupancy` (texture3d),
`cloud_smp` (sampler), `out_tex` (storage2d rgba16f), `U` (read `array<f32>`
uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_raymarch_v3", ...)` (textures/sampler/storage texture require resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_noise_v3
Volumetric-cloud noise fill (Schneider/Hillaire style), v3 variant. Low-frequency
FBM base silhouette + high-frequency FBM surface erosion for the billowed cumulus
look, with altitude shaping (round bottom, full middle, anvil top). Writes the 3D
cloud density texture.
Bindings (see bindings.yaml): `clouds` (storage3d rgba16f — the cloud volume), `U`
(read `array<f32>` uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_noise_v3", ...)` (storage texture requires resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).
·computeshader · born here
❒asset # cloud_raymarch_v4
Volumetric-sky cloud raymarcher, v4. Adds procedural detail noise computed
in-shader on top of the large-scale texture silhouette (removes v3's voxel-grid
blockiness) and uses multi-distance shadow sampling (4 geometrically spaced strides)
instead of a cone trace. Reads the cloud volume + occupancy textures and writes the
lit raymarch result.
Bindings (see bindings.yaml): `clouds` (texture3d), `occupancy` (texture3d),
`cloud_smp` (sampler), `out_tex` (storage2d rgba16f), `U` (read `array<f32>`
uniforms).
Usage: dispatch by identity with `compute.dispatchEx("@builtin::systems.volumetrics.shaders.cloud_raymarch_v4", ...)` (textures/sampler/storage texture require resource binding, not positional buffers) —
it resolves to the shader's stable guid and auto-compiles on first use (no setup).