Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·part ofpackage volumetrics.package·originates fromworld 07158574-5…

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.

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

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

-- 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.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

volumetricSky Module Procedural volumetric clouds rendered with compute-shader raymarching. A complete sky-renderer system built on the generic GPU primitives exposed through `compute.*` (3D textures, 2D storage textures, compute shaders with texture bindings). Serves as a worked example: nothing in this package needs Rust changes — the noise generator and the raymarcher are both WGSL files in the package's `shaders/` directory, hot-reloadable through the VFS. The flow: 1. A 3D texture is allocated (default 256×128×256 RGBA16F). 2. The `cloud_noise` compute shader fills it with FBM noise (procedural — no CPU upload). 3. The raymarcher (`cloud_raymarch_v3/v4/v5`) walks per-pixel rays, sampling the cloud volume, doing Beer-Lambert absorption, short shadow rays to the sun, and the Henyey-Greenstein phase function. Output writes into a screen-sized 2D storage texture. 4. A TAA pass blends current+history into the final RT. 5. The result is composited via a post-process effect (or sampled by a material — your choice). Quick start — attach to any entity at the world origin: local Sky = require("@builtin::systems.volumetrics.volumetricSky") local ent = entity.spawn("sky").id entity(ent).component.add("VolumetricSky", { preset = somePreset }) The component drives `M.spawn` / `M.fillClouds` / `M.dispatchRaymarch` automatically; direct M.* use is rare except in custom integrations.

copyPreset(src: { [string]: any }) → void

A preset table a caller owns outright. Every value in a preset is a number or a short array of numbers, so one level of array copying detaches the result from the table it was read out of — a caller tweaking what it got back leaves the component's live preset, and these defaults, alone.

argtypedescription
src{ [string]: any }

sortedNames(set: { [string]: boolean }) → void

The names a set holds, in a stable order, for an error message to list.

argtypedescription
set{ [string]: boolean }

presetValueFault(key: string, value: any) → string

What is wrong with the value given for a weather parameter, or nil when nothing is. The defaults declare each parameter's shape — a number, or an array of that many numbers — and the cloud shaders read every parameter as the shape its default carries, so a value of another shape is named here and refused rather than stored.

argtypedescription
keystring
valueany

settableFields(comp: any) → void

The component fields a caller may set by name, read off the live component. A sealed proxy enumerates exactly what `public` declared plus the engine's own entries, so a refusal names the set the schema holds rather than a copy of that set kept here. The callables among them are the component's error surface, which takes no value.

argtypedescription
company

applyOptions(comp: any, opts: { [string]: any }, consumed: { [string]: boolean }?) → void

Apply an options table to a live sky component. A key the weather configuration carries is folded into a copy of the component's current preset, at the top level or inside a `preset` sub-table alike, and the whole table is reassigned once — an index mutation would not replicate. Every other key names a top-level component field and is written by name. A key that is neither raises, naming the key alongside both sets it could have come from, so the option a caller meant is in the message. A weather parameter whose value is not the shape that parameter takes raises the same way, because the component's `preset` is one table field and the schema types the table rather than the parameters inside it. `consumed` names the keys the caller has already answered for — the entity's `name`, for the call that mints one. A key outside that set is the sky's business and is routed or refused here.

argtypedescription
company
opts{ [string]: any }
consumed{ [string]: boolean }?

takeWeather(key: string, value: any) → void

argtypedescription
keystring
valueany

defaultPreset( ) →

A fresh copy of the sky's default weather configuration — every shaping, placement and lighting parameter the cloud layer reads, at the values a sky spawned with no options carries. The table is the caller's to edit and hand to `M.spawn`.

examples

local p = M.defaultPreset(); p.coverage = 0.6; M.spawn({ preset = p })

v3( ) → void

v4( ) → void

v5( ) → void

default( ) → void

getShaderHandles(variant: ?) → void

argtypedescription
variant?

ensureVolume(opts: ?) → void

Resource lifecycle

argtypedescription
opts?

ensureRenderTarget(w: number, h: number, scale: number) → void

The cloud layer's render targets, as ordinary render targets the passes read and write. They are ONE size for every view the frame draws, and deliberately not screen-tracking. A target that resizes to whatever camera is on is empty for the first render at each new size, and an environment cube is six renders at a size nothing else uses — so every face composited the target before anything had written it at that size, and the clouds came out black in the reflection while the sky above was white. The composite reads the layer by UV, so one size serves a viewport, a capture and a cube face alike.

argtypedescription
wnumber
hnumber
scalenumber

fillClouds(params: { [string]: any }) → void

Dispatch the active `cloud_noise_*` shader against the cloud volume, filling it with FBM density + colour based on `params`. Called every frame by the component (wind drift advects per-frame). `VolumetricSky.component:snapshotNoiseParams()`. Keys include `cloudSize/cloudHeight, noiseScale, octaves, coverage, densityGain, altitudeLow/altitudeHigh, anvilBias, detailScale/octaves/strength/ threshold, baseLowRemap/baseHighRemap, color, windX/Y/Z, seed, cloudType, worleyBlend, shaderVariant`.

argtypedescription
params{ [string]: any }Flat noise-shaping table — typically the output of

examples

M.fillClouds(component_snapshot_table)

dirNormalize(v: ?) → void

Raymarch dispatch — per-frame.

argtypedescription
v?

quatToRotMat(q: ?) → void

Build a row-major 4x4 inverse view-projection matrix from a camera snapshot. Returned column-major (16 floats) to match WGSL `mat4x4<f32>` column-major upload convention used by `cloud_raymarch.shader`. The engine doesn't expose its actual view-projection through Luau yet, so we reconstruct one from `layers.active.camera`'s position + rotation quaternion + fov + aspect. The scene camera projects through a reversed clip range: NDC.z ∈ [0, 1] with near = 1 and far = 0, matching the scene depth buffer. The camera looks along its local -Z, +Y up.

argtypedescription
q?

invViewProj(cam: ?, aspect: ?, fov_deg: ?, near: ?, far: ?) → void

argtypedescription
cam?
aspect?
fov_deg?
near?
far?

viewProjFromCam(cam: ?, aspect: ?, fov_deg: ?, near: ?, far: ?) → void

Forward view-projection from the same camera snapshot — used by TAA to reproject world-space points into the previous frame's screen space. Produced in the same column-major convention as `invViewProjFromCam`, so the WGSL shader uploads it identically. Construction is the analytic inverse of invViewProjFromCam: we build a view matrix (camera→world is RT; world→camera is its inverse) and a perspective matrix over the same reversed clip range, and multiply.

argtypedescription
cam?
aspect?
fov_deg?
near?
far?

centreRayVolumeDistance(camPos: ?, fwd: ?, aabbMin: ?, aabbMax: ?) → number

The distance the TAA pass carries history through. The pass picks ONE world point along each view ray and uses it to find where this pixel stood in the previous frame. A point at distance `d` travels `translation / d` across the screen when the camera translates, so the anchor has to stand where the cloud stands: an anchor metres from the lens moves a large fraction of the screen for a step the cloud kilometres away barely registers, and the history then arrives from a different part of the picture. Under the reversed clip range this module projects through — near at NDC z = 1, far at z = 0 — the relation is z = near * (far - d) / (d * (far - near)) so the middle of the [0, 1] band sits at two near planes. The distance the cloud stands at is the middle of the view ray's own traversal of the volume. `centreRayVolumeDistance` returns it for the ray through the centre of the frame, falling back to the range of the volume's centre for a ray that leaves the volume behind (a camera pointed at the ground, or standing outside the slab looking away).

argtypedescription
camPos?
fwd?
aabbMin?
aabbMax?

dispatchRaymarch(params: { [string]: any }, viewport: { width: number, height: number }) → void

Dispatch the active `cloud_raymarch_*` shader, blend the result with the prior frame via the TAA pass, and write the composited output into `scene_volumetric_sky`. Called every frame by the component after `M.fillClouds`. The output is one full-screen layer for the ONE view named in `params.camera`, and the frame composites it over everything that frame drew, so the call belongs in the frame's deferred phase (`task.defer`) where the camera every other script has written is the camera the renderer is about to read. Issued from a component `update`, it renders the layer for whatever the camera was midway through the frame, and a camera moved later in that same frame gets a layer belonging to somewhere else. `VolumetricSky.component:snapshotRenderParams()`. Includes camera, AABB envelope, density / phase, sun/sky lighting, step/maxSteps quality knobs, taaBlendAlpha, shaderVariant.

argtypedescription
params{ [string]: any }Flat raymarch params — typically the first return of
viewport{ width: number, height: number }`{ width, height }` of the target render texture.

examples

M.dispatchRaymarch(component_render_table, { width = 1280, height = 720 })

enqueueCloudPasses(ctx: any) → void

Enqueue the cloud layer's passes for the frame being drawn. Called by the `volumetricSky` render feature once per frame; the renderer then runs them for EVERY view the frame draws — the viewport, a capture, and each face of a reflection probe's environment cube. The chain is: raymarch the layer for the camera being drawn, copy it to the target the composite reads, temporally accumulate over that copy where there is a history to accumulate against, and lay the result over the frame behind whatever geometry the depth buffer holds. The accumulation is the one part that is not about the scene: it blends against what the PREVIOUS frame of the same view held, and a cube face is rendered once with no previous frame of its own. So it stays out of environment captures, and the copy before it is what those faces read.

argtypedescription
ctxanyThe render feature's frame context.

examples

M.enqueueCloudPasses(ctx)

ensureCloudPrograms( ) → void

Register the two fragment programs the layer's passes draw with, and put back either one the chain has lost. Called by the render feature on every frame before its enqueues, so a program taken out from under a standing sky — by a caller, by a scene handing the engine back the chain it was found with — is registered again on the next frame rather than leaving the layer with a pass that draws with nothing. They are registered DISABLED: the chain never runs them, and they are there so a render pass can name a program. What draws them is the pass.

examples

M.ensureCloudPrograms()

releaseCloudPasses( ) → void

Release the layer's render targets and its fragment programs. Called by the render feature's teardown.

examples

M.releaseCloudPasses()

spawn(opts: { [string]: any }?) → string

Spawn the singleton VolumetricSky entity, or configure the one already standing. Every weather parameter (`coverage`, `sunDir`, `sunColor`, `sunIntensity`, `windSpeed`, …) is settable at the top level or inside a `preset` sub-table, and every `VolumetricSky` component field (`shaderVariant`, `cloudSize`, `taaBlendAlpha`, …) by its own name; `name` names the entity. An option that is neither raises, listing both sets. 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. Weather parameters left out keep the sky's current values. `name` for the entity's own.

argtypedescription
opts{ [string]: any }?Optional. Weather parameters and component fields, by name, plus

examples

local skyId = M.spawn({ coverage = 0.45, sunIntensity = 4.0 })

configure(opts: { [string]: any }) → boolean

Apply sky options to the sky already standing — the weather parameters and component fields `M.spawn` takes, against an existing entity. Parameters left out keep their current values; an option that names neither a weather parameter nor a component field raises, listing both sets, and so does a weather parameter given a value of the wrong shape. none has been spawned.

argtypedescription
opts{ [string]: any }Weather parameters and component fields, by name.

examples

M.configure({ coverage = 0.6, sunIntensity = 2.0 })

find( ) → string

Look up the singleton sky entity by its conventional name.

examples

local id = M.find(); if id then entity.despawn(id) end

despawn( ) → void

Despawn the singleton sky entity. Does NOT free GPU resources — call `M.destroy()` for that.

examples

M.despawn()

withComp( ) → any

Convenience setters — mutate the singleton component's fields. All return `true` on success, `false` when no sky entity exists. Two distinct field spaces (see VolumetricSky.component): the shaping params (sun, coverage, density, windSpeed, …) live INSIDE the one Sync'd `preset` table; only `wind` (animated drift), `shaderVariant`, and the NoSync perf knobs are top-level fields. `setProp` writes a top-level field directly; `setPresetProp` reads the current preset, updates one entry, and reassigns the WHOLE table so the Sync setter fires (index-mutation `preset.k = v` would not replicate — README).

setProp(key: string, value: any) → boolean

argtypedescription
keystring
valueany

setPresetProp(key: string, value: any) → boolean

argtypedescription
keystring
valueany

setCoverage(c: number) → boolean

Update `preset.coverage` on the active sky entity.

argtypedescription
cnumberNew coverage [0..1] — higher = sparser clouds.

examples

M.setCoverage(0.5)

setDensity(g: number) → boolean

Update `preset.densityGain` on the active sky entity.

argtypedescription
gnumberDensity gain (v3/v4: baked extinction; v5: σ_t per unit length).

examples

M.setDensity(0.45)

setSun(dir: { number }, color: { number }?, intensity: number?) → boolean

Update the sun direction (and optionally colour + intensity).

argtypedescription
dir{ number }Sun direction vec3 `{ x, y, z }` — pointing toward the sun.
color{ number }?Optional sun colour `{ r, g, b }`.
intensitynumber?Optional HDR intensity (soft-clipped at 0.85 in tonemap).

examples

M.setSun({ 0.5, 0.5, 0.5 }, { 1.0, 0.95, 0.85 }, 5.5)

setWind(v: { number }) → boolean

Update the animated wind offset on the active sky entity.

argtypedescription
v{ number }Wind vec3 `{ x, y, z }` in noise space.

examples

M.setWind({ 0.5, 0.0, 0.3 })

destroy( ) → void

Release everything the cloud layer holds: the volume texture, the layer's render targets, the uniform buffers, and the programs its passes draw with. The frame after this call is the frame the engine draws without a cloud layer in it, and a sky put up afterwards builds each of them again. Call from the component's `onDespawn` or directly when tearing down the sky.

examples

M.destroy()

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

43items
·
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).
▲ 0↑ born
·
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).
▲ 0↑ born
✦
shader · born here
❒asset
▲ 0↑ born
·
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).
▲ 0↑ born
·
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).
▲ 0↑ born
·
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).
▲ 0↑ born
·
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).
▲ 0↑ born
·
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).
▲ 0↑ born
✦
shader · born here
❒asset
▲ 0↑ born
·
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).
▲ 0↑ born
❒
package · born here
❒asset
# 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.
▲ 0↑ born
backing path · systems/volumetrics.package/volumetricSky.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.