NDVDB docs · 1.0
NDVDB - Cinematic OpenVDB Importer & Real-Time Renderer · for Unity URP

Real volumetric smoke, fire and explosions. Composited like they belong.

Import and play your own OpenVDB sequences as real-time lit volumetrics: GPU streaming, joint multi-volume compositing, cross-volume shadows, wave-accurate water integration, and Timeline support.

What NDVDB is

NDVDB is a VDB importer and player. You author simulations in Houdini, EmberGen, Blender or any OpenVDB-compliant tool, convert the .vdb sequence once in the editor into .ndvdb, NDVDB's own GPU-ready streaming format, and play the result back in Unity as a streamed, real-time lit volume. There is no baking to flipbooks, no mesh proxy, and no cloud step.

.vdb sequenceHoudini · EmberGen · Blender
→
NDVDB Converterquality tier + grid roles
→
.ndvdb assetsparse bricks, temporal deltas, zstd
→
NDVDB PlayerGPU streaming, RAM budget
→
Scene Compositorjoint march, shadows, water

Feature map

Requirements

Unity6000.x with the Universal Render Pipeline (URP 17+). The compositor is built on RenderGraph.
Packagescom.unity.render-pipelines.universal and com.unity.timeline. Both assemblies reference Timeline directly, so the package must be installed even in projects that never use a Timeline; the store import adds it when it is missing, and the Package Manager lists it under Unity Registry otherwise.
GPUCompute-capable desktop GPU. D3D12, Vulkan or Metal.
PlatformsWindows, Linux and macOS standalone. Mobile and console are not supported in this release.
ConvertingThe Converter runs in the Unity Editor on Windows x64 only: the OpenVDB reader ships as Windows DLLs, editor-only and never part of a build. Playback has no native dependency, so a team on Mac or Linux converts on one Windows machine and copies the imported folder (the .ndvdb, its prefab and their .meta files) into the project. On other editors the Converter window says so instead of opening.
PipelinesThe compositor and water integration are URP-only. An experimental HDRP subshader ships for per-volume playback and is unofficial.

What NDVDB is not

  • Not a fluid simulator. It plays back simulations you author in your DCC.
  • Not a runtime-authorable field. Volumes are streamed effects. You can move, scale, time-remap and light them, but not edit voxels at runtime.

One-time purchase. Full C# and shader source included. No subscription. Redistributes permissively licensed open-source components (Apache-2.0 / BSD / MIT); full notices are in the package.

Start

Getting Started

From a fresh URP project to a playing volume in five steps. The Setup window does the project plumbing for you.

  1. Import the package

    Import NDVDB into a Unity 6000.x project that uses URP. The package contains the runtime, editor tooling, shaders, and this documentation under Assets/NDimensionalGames/VDBImporter.

  2. Run NDVDB Setup

    Open Tools ▸ NDVDB ▸ Setup. It checks that URP is active, that every URP renderer has the NDVDB Composite renderer feature, and that the depth texture is enabled. Each item has a one-click fix, or use Fix All Renderers.

  3. Convert a VDB sequence

    Open Tools ▸ NDVDB ▸ Converter, point it at your .vdb folder, pick a quality tier and map grid roles. Convert & Import writes an .ndvdb asset and hands you a prefab with an NDVDB Player already configured. See Converting VDBs.

  4. Drop the prefab in the scene

    Place it, scale it if needed, and press Play. The first frame streams instantly; the IO thread reads ahead while you watch. In the editor you can scrub without entering Play mode via the player's Preview Controls.

  5. Light it

    Your directional light drives self-shadowing and scene shadows. Point and spot lights light the smoke automatically. If the sim has a flame grid, it glows with blackbody colour and can optionally emit lights. See Lighting & Fire.

✓

The player inspector re-checks setup. Under Playback Readiness each NDVDB Player reports whether the compositor feature is present on the renderer that draws it, and offers the same fixes as the Setup window.

Recommended scene setup

  • One directional light as the main light. Its shadows are read by the march so geometry shadows the smoke and the smoke shadows the scene.
  • Depth texture on for the URP asset. Setup enables it. Transparents and water sort against the smoke through it.
  • HDR colour so blackbody flame and bright scatter keep their range through tonemapping.
  • Keep the camera's near plane sane. Volumes the camera passes through are supported, but very small near planes waste march steps.

Where to go next

Start

Converting VDBs

The Converter turns an OpenVDB sequence into a single .ndvdb asset: sparse bricks, per-brick quantization, temporal deltas and a lossless Zstandard envelope. It runs in the editor on every core, streams the source files once, and writes atomically.

Supported input

  • A folder of numbered .vdb files (one per frame) or a single .vdb for a static volume.
  • Several sequences in one folder. Files whose names differ only by their frame number form one sequence. When the folder holds more than one (two exports side by side, or an export plus a stray file), a Sequence dropdown appears under the folder on both tabs, with the largest sequence selected; one conversion takes one sequence, and the Asset Name follows the pick. A warning under the dropdown lists missing or repeated frame numbers, so a half-copied folder does not quietly become a stuttering asset.
  • Standard OpenVDB float grids, and 16-bit half grids (Houdini's 16-bit float volumes, OpenVDB 13 file format 225), which are promoted to float on load. Double, integer and vector grids other than velocity are not accepted; export as float from your DCC. The converter reads with OpenVDB 13.1, so files from current Blender and Houdini releases open without version warnings.
  • Any exporter that writes compliant OpenVDB: Houdini, EmberGen, Blender, Axiom, and others.
  • Blender fluid caches. Blender writes its .vdb frames in domain-local space: voxel (0,0,0) is the domain's corner and the file carries no world position, so on its own a cache would pivot at that corner. The converter reads the cache's sibling config folder (config_####.uni, written with every bake) for the domain object's world matrix, the true cell size and, for adaptive domains, each frame's region, then bakes the true placement into the asset: the pivot is the Blender world origin and adaptive frames sit in one fixed lattice. (Adaptive caches need this: Blender stamps each of their frames with a different, wrong voxel size, so without the config folder they cannot be placed at all.) Point the converter at the data folder and keep config beside it when you copy a cache. A rotated domain is reported in the Console; apply that rotation to the GameObject. Blender Cache Origin (Advanced tab) turns this off.
  • Up axis. Houdini and Maya are Y-up; Blender, EmberGen exports, 3ds Max and Unreal are Z-up. Source Up Axis (Advanced tab) defaults to Auto, which reads the file's creator tag and logs its choice. A Y-up file converted as Z-up lies on its side; if you already converted one that way, a −90° X rotation on the GameObject is the equivalent fix.
  • Units and scale. A file unit is taken as one metre. Source Units (both tabs) rescales at import: Centimetres for Unreal-targeted exports and for EmberGen sims that were built around a centimetre FBX imported at 100%, or a custom factor. EmberGen has no fixed unit and applies an FBX's node scale but not its unit, while Unity's importer converts centimetre files to metres; importing the model into EmberGen at 1% instead of 100% makes new sims metre-true from the start. The choice becomes the header's import scale, so voxel data is untouched and the value stays editable under Import Transform in the asset inspector. Under the folder the converter shows the size sampled frames (the first with data, the middle and the last of the range) would have in the scene, and flags EmberGen exports wider than 200 m.
  • Empty frames. EmberGen writes 1 KB placeholder files for frames before the first emission, and whenever nothing is emitting. They are kept as empty frames so timing is preserved and the player draws nothing for them; the scan detects grids from the first frame that holds data and says how many empty frames lead the range, and the Console's metadata-scan line counts them apart from real full opens. A range in which every frame is empty stops with an error instead of producing an asset.

Quality tiers

TierWhat it doesUse it for
DraftFastest conversion, loose temporal deltas, no packing.Look-dev iteration where you re-convert often.
StandardLossless zstd packing. Best size/quality balance for typical smoke and fire.Most effects. The default.
CinematicNear-lossless temporal encoding that preserves subtle wisps and ember motion between frames, with periodic I-frames. Larger files.Hero shots and close-ups.

All tiers store voxels at 8 bits per brick with a per-brick range, which gives roughly 11 to 12 bits of effective precision. The tiers differ in how aggressively frame-to-frame deltas are pruned and whether the container is packed; the exact values are listed under Advanced tab.

Conversion speed

Conversion is a frame pipeline. Every worker thread reads a frame and then encodes whichever frame just became ready, so temporal deltas, quantization, checksums and packing of different frames run on all cores at once while the file is written in order. A 200-frame EmberGen explosion (1.3 GB of .vdb, two grids) converts in about 9 seconds from cache on a 16-thread desktop; the previous serial encoder needed just under three minutes for the same files.

  • Header-only scan. The pass that establishes the sequence's domain reads each file's grid metadata (a fraction of a millisecond per file) instead of decompressing it.
  • One sequential stream. Source files are streamed once, in order, through the OS cache before they are parsed, so a spinning or USB drive is never seek-thrashed. On such a drive the first read of the sequence sets the floor: about 30 seconds for 1.5 GB at 50 MB/s.
  • Only the mapped grids are read. Velocity, shadow and other unmapped grids are skipped without being decompressed.
  • Parallel VDB Reads (Advanced tab, auto = 4) caps how many files OpenVDB parses at once. Encoding always uses every core; file parsing stops scaling after a few threads, so this is separate from Parallel Workers. Lower it on a small laptop, raise it on a large workstation with fast storage.
  • Memory is about two frames of raw bricks per worker, trimmed to 35% of physical RAM from the heaviest frame the scan saw.
  • Every conversion logs one [NDVDB Converter] Profile: line to the Console with the time spent in disk, parsing, deltas, quantization, checksums, packing and writing. Read it before changing any setting.

Grid roles

NDVDB renders three roles. Map each grid in the file to one of them, or leave a grid unmapped to drop it from the output.

RoleDrivesTypical grid names
DensityOpacity, scattering, shadows.density, smoke
FlameBlackbody emission and flame-driven lights.flame, fire, fuel
TemperatureOptional. Colours the flame by temperature instead of flame intensity. Values are stored as exported; the converter records each grid's maximum in the asset, and the material's Temperature Range Max defaults to it.temperature, heat

Output and import

  • Convert & Import writes the .ndvdb file into your project, imports it as an NDVDB Asset, and creates a prefab with an NDVDB Player and a volume material.
  • Asset Name and Import Folder (Simple tab). The name follows the source folder's name until you type one (clear it to go back), or the sequence's own name when the folder holds several; exporters that always use the same folder name, such as Blender's data, are the case for it. The folder defaults to Assets/NDimensionalGames/Imports/<Asset Name>; Browse picks any folder inside the project's Assets, Default returns to it. The Advanced tab has a free Output Folder and Filename for writing outside the project.
  • Writes are atomic: the file appears only when complete, so a Unity import triggered mid-conversion never sees a partial asset.
  • The asset inspector shows frame count, brick statistics, grid roles, and the world-space bounds and voxel size read from the VDB. Its Import Transform (a uniform scale about the simulation origin plus a translation) fixes unit and placement mismatches after import: it is patched into the header, the voxel data is untouched, and every scene player rebuilds its domain live.
  • Scale and origin are preserved. Enable Use VDB Origin on the player to place the volume exactly where it sat in the simulation.
i

Footprint. Expect hundreds of megabytes where the raw VDB cache is multiple gigabytes. Sparse bricks skip empty space entirely, and temporal deltas store only what changed.

Advanced tab

The Simple tab is three presets over the controls below. Switch to Advanced when a sequence needs something the presets do not cover. Every value is baked into the asset at conversion time; nothing here is read at playback.

PresetDelta ThresholdLossless PackingI-Frame Interval
Draft0.008off0
Standard0.004on0
Cinematic0.0005on12

Source

ControlWhat it does
Folder / File PatternThe folder is scanned for files matching the pattern (default *.vdb). The matches are grouped into numbered sequences and ordered by frame number, so frame_2 precedes frame_10. The pattern hides files before that grouping, for example smoke_*.vdb.
SequenceShown only when the matches form more than one sequence. Picks the one to convert (the largest by default) and resets the frame range to the whole sequence. Below it, a warning lists missing or repeated frame numbers.
Start Frame / End FramePositions in the chosen sequence's file list, counted from 0 and inclusive; End Frame −1 means the last file. They trim the range, they do not renumber it.
Match Source OrientationOn by default. The raw VDB to Unity axis swap is a mirror; this flips X and Z so the result is a proper rotation and chiral motion (curling trails, swirls) turns the way it was authored. Turn it off only to byte-match assets converted before the option existed.
Source Up AxisAuto, Z-up or Y-up, as described under Supported input. The Console logs the choice.
Source UnitsAs exported / Centimetres (×0.01) / Custom. Written into the header's import scale at convert time, the same value the asset inspector's Import Transform edits later; voxel data is untouched. Shown on both tabs with the sampled frames' size at that choice.
Blender Cache OriginAuto reads a Blender cache's config folder for the domain's world placement and true cell size; Off converts the files as written. See Supported input.
VDB Grids to ExportThe grids of the first file, with their type. Tick Export and pick Maps To (see Grid roles): one grid must be Density, each role at most once, a Temperature grid needs a Flame grid. Unticked grids are never decompressed. Scan Folder rereads the list after you change the folder or pattern.

Codec

ControlWhat it does
All-I-Frame ModeStores every frame as a complete snapshot. A seek, loop wrap or reverse step then costs one frame read. With P-frames the player composites the chain from the nearest I-frame at or before the target, from its RAM cache or from disk on a cold jump, into a single upload. Files are roughly two to three times larger. Worth it for heavy scrubbing, reverse playback or Timeline shots that jump around a long sequence; forward playback gains nothing.
I-Frame IntervalWrites a full frame every N frames. 0 means the first and the last frame only: the last frame is always an I-frame so a loop restarts with one read. A cold seek composites at most N frames, so the interval is the seek cost you are willing to pay; 24 gives a full frame every second at 24 fps.
Delta ThresholdA brick is rewritten in a P-frame when any voxel in it moved by more than this, in the grid's own units (density is usually 0 to 1). Bricks that vanish are always recorded, so nothing lingers. Lower keeps subtle drift and ember motion at the cost of larger files.
Lossless Packing (zstd)Wraps each frame in a zstd envelope at level 1. Decoded bricks are bit-identical; files shrink a further 1.5 to 3 times. Decoding runs on the streaming thread, so playback cost is unchanged and slow disks read fewer bytes. Draft turns it off for the fastest reconversion loop.

Resolution

ControlWhat it does
Downsample / Target Resolution / Maintain Aspect RatioResamples every frame trilinearly to the target grid. With Maintain Aspect Ratio the target is fitted inside the source box (64³ on a 176×128×176 sim becomes 64×47×64). This goes through the dense path, expanding the whole domain per frame, so it is slower and uses more memory than the normal brick path. Use it for distant or mobile variants of a heavy sim.

Output

ControlWhat it does
Output Folder / FilenameAnywhere on disk. Only a folder inside the project's Assets is imported by Unity and can receive the prefab.
Frame RateWritten into the asset as its native rate; the player and Timeline clips play at it unless told otherwise.
Create PrefabAfter the import, a prefab next to the asset with a player, the default volume material and the compute shader wired, at 1:1 world scale. Never overwrites an existing prefab.
Temp FolderWrites the file as name.ndvdb.tmp in that folder and moves it into place when complete. Useful when the output folder sits on a network or slow drive. Left empty, the file is written next to its destination as name.ndvdb~ and renamed, so the output is atomic either way. The panel below it shows free space on the drives involved against the estimate.

Performance

ControlWhat it does
Parallel Workers0 means every logical core. Each worker reads a frame and encodes whichever frame is ready; peak memory is about two raw frames per worker, trimmed to 35% of physical RAM.
Parallel VDB ReadsCaps concurrent OpenVDB file parses (auto = 4). See Conversion speed.
Estimates and PreviewConversion time from a conservative 60 MB/s of source, peak RAM from the frames in flight, and an output size from an empirical ratio for the chosen codec settings. All appear after a scan. The Console's Profile: line after a conversion is the real measurement.
Core

NDVDB Player

The component that streams an .ndvdb asset from disk, keeps decoded frames in a RAM-budgeted cache, uploads the current frame into a sparse GPU atlas, and hands the volume to the compositor.

Playback

SettingMeaning
Ndvdb AssetThe converted sequence.
Frame Rate 24Playback rate in frames per second. Timeline clips can override it per clip.
Loop onWrap at the last frame.
Play On Start / Play On EnableAuto-start in Play mode. Use Play On Enable for pooled or re-activated effects.

Scripting: Play(), Pause(), Stop(), TogglePlayPause(), SetFrame(int), SeekToFrame(int), SetTime(float seconds), SetProgress(0..1), PrewarmCache(), plus read-only CurrentFrame, PlaybackProgress, GetDuration(), CachedFrames, and IsComposited (true while the scene compositor drew this volume in the last frame).

Editor preview

The Preview Controls section plays, pauses, loops, frame-steps and scrubs the sequence in the Scene view without entering Play mode. Scrubbing is an undoable operation ("Scrub NDVDB Frame"), so a preview position survives an accidental change.

Streaming and memory

SettingMeaning
Stream Lookahead 16Frames the IO thread decodes ahead of the playhead. Raise for very large frames or slow disks.
Max Cache Ram MB 0 = unlimitedUpper bound for the decoded-frame cache. At 0 the whole sequence stays resident once streamed, so scrubbing, looping and Timeline never touch disk again. Set a budget when many large sequences play together: frames farthest behind the playhead are evicted first and re-stream on the next pass; the playhead and lookahead window are never evicted.
Use Sparse Atlas onUpload only occupied bricks into a shared atlas with an indirection table. Off falls back to a dense 3D texture per volume.
Enable Empty Space Skip onThe march walks an occupancy grid and skips empty bricks.

The Frame Cache section shows resident frames, the IO queue depth and the memory in use, so you can size the budget against your target machine.

i

Off-screen volumes keep their memory on purpose. An enabled player keeps its frame cache and GPU data current so the camera can pan onto it without a hitch; it takes no compositor slot and builds no caches while unseen. To release the memory itself, disable the player or drive it from a Timeline Activation track.

Placement and scale

SettingMeaning
Use VDB OriginOffset the volume so the simulation's world origin lands on this transform. Lines up with assets exported from the same scene.
Fit Natural World SizeScale the volume to the physical size recorded in the VDB (voxel size × resolution) instead of a unit cube.
Auto Depth Sort onOnly relevant to the per-volume fallback path. The compositor sorts jointly and skips this.

Non-uniform domains are supported. A long, thin missile trail keeps its aspect ratio and is sampled sparsely along its length.

Material

Each player renders with a material using the VDB Volume shader. Material Template is the shared source; Instance Material, on by default, gives this player its own in-memory copy of it, so one fireball can run hotter, darker or a different colour without touching the others that share the template. A player added without any material receives the package's default volume material as its template automatically, and the template auto-captures whatever VDB material you drop on the renderer. Tweaks made on the copy last for the session; make lasting looks on the template or a material variant. See Volume Material.

Override Emission Properties exposes Blackbody Tint and Blackbody Intensity on the player, which is handy when several effects share a material but one needs a hotter fireball.

Water compositing

Water Compositing chooses how this effect relates to water in the scene: Depth Sorted (default, sorts by depth like any transparent), Show Above, Show Below, or Surface Split (renders both halves). The Water page covers the modes in detail.

Proxy Mode

Proxy Mode is an editor-only layout aid: Inherit follows the session-wide Layout Mode level, or force Full, Preview, Brick or Bounds Only for this player. It never affects Play mode, recording or builds. See Layout Mode.

Core

Volume Material

The VDB Volume shader holds the look of an effect. The compositor reads these values per volume, so every setting here applies whether the volume is drawn jointly or by the fallback path.

Density

PropertyRangeMeaning
Density Multiplier0 – 100 3Scales the density grid. Thin sims such as missile or rocket trails often need 20 to 60 to occlude convincingly.
Density Bias0 – 1 0.02Subtracted before scaling. Cleans low-level noise and tightens the silhouette.
Step Size0.001 – 0.1 0.015March step in volume space for the fallback path. The compositor uses its own stepSize.
Max Stepsint 192Step cap for the fallback path.

Smoke shading

PropertyRangeMeaning
Smoke Scatter ColorcolourAlbedo of the smoke under light.
Smoke Absorption0 – 20 8How quickly light is absorbed. Higher = darker, more opaque smoke.
Scatter Coefficient0 – 20 4How much light is scattered toward the camera. The absorption/scatter ratio sets the smoke's brightness.
Anisotropy-0.99 – 0.99 0.25Henyey-Greenstein phase. Positive favours forward scattering: bright rims when the light is behind the smoke.
Multiple Scattering0 – 3 0.3Approximates light bouncing inside the volume. Lifts dense smoke toward its albedo independent of density, which is how you get bright white cumulus rather than grey soup.
Multi Scatter Albedo TintcolourTints the multiple-scattering contribution only.
Powder0 – 1 0Beer-powder term on the single-scatter sun light: front-lit edges and crevices, where the sun has barely attenuated, go darker than the body, which is the crumpled, pillowy look of real cumulus. Weighted to front lighting (sun behind the camera) and fading out toward the sun. Keep Multiple Scattering above zero with it so the edges darken without hollowing. A few instructions per sample; no extra marches.

Ambient

PropertyRangeMeaning
Ambient SourceMaterial Color / Scene Ambient Material ColorWhere the sky and ground colours come from. Scene Ambient reads the scene's Environment Lighting (the ambient probe in Skybox, Gradient or Color mode) straight up for the sky term and straight down for the ground term, so the smoke sits in the same hemisphere light as the rest of the scene and follows the skybox as you change it. Ambient Color and Ground Bounce Color are ignored then; the two intensities still scale the result (1 = as the scene). The probe is evaluated on the CPU once per frame, so this costs the GPU nothing. In Skybox mode the probe must exist: generate lighting once (or leave Auto Generate on) after changing the skybox.
Ambient Intensity / Ambient Color0 – 2 0.18Sky fill. Keeps shadowed smoke from going black. With Ambient Source = Scene Ambient, start at 1.
Ground Bounce Intensity / Ground Bounce Color0 – 2 0Light bounced up from the ground, the mirror of the sky term: occluded by a march straight down through the density at the Ambient Sky Occlusion strength, so undersides pick up the ground colour while tops keep the sky. With Scene Ambient the colour is the probe's downward value (warm over sand, green over grass). 0 = off and free; on, it is served from the light cache when that is enabled, otherwise it costs one extra occlusion march per sample, the same as the sky term.
Ambient Sky Occlusion0 – 1 0Darkens the fill inside the volume by marching toward the sky. The occlusion is softened by the same multiple-scattering octaves as the sun term, so with Multiple Scattering above zero interiors go grey rather than black. Served from the light cache when enabled, so it is cheap.

Fire

PropertyRangeMeaning
Blackbody Intensity0 – 100 15Emission strength of the flame grid.
Flame Threshold0 – 1 0.05Flame values below this do not emit. Trims the faint halo around a fireball.
Flame Falloff0.1 – 5 1.5Curve from threshold to full emission.
Use Temperature Grid for Colourtoggle offWith a temperature grid mapped, the flame takes its colour from temperature and its intensity from the flame grid. The compositor's Flame Emission and the Flame Lights follow the same ramp. Ignored when the asset has no temperature grid.
Temperature Range Max0 = asset maxTemperature value that reaches the top of the flame colour ramp. At 0 the player uses the maximum the converter recorded for the grid, so EmberGen exports (about 0 to 2.3) and Kelvin exports (thousands) both use the whole ramp. Set it explicitly to pin the look across assets.
Flame Illum Radius (UVW)0 – 0.3 0.08How far the flame grid illuminates surrounding smoke inside the same volume. This is what makes a fireball glow through its own soot.

Self-shadow and additional lights

PropertyRangeMeaning
Shadow Steps / Shadow Step Sizeint / 0.01 – 0.5The main-light self-shadow march. With the light cache on, this is evaluated once per frame per brick instead of per sample.
Shadow Density Scale0 – 10 2.5Density used for self-shadowing only. Raise for punchier contrast without thickening the smoke.
Shadow March Blur (mip LOD)0 – 3 1Sample a blurrier level for shadows: softer, cheaper, less noise.
Max Additional Lights Per Sample0 – 8 4Cap on point/spot lights evaluated per sample.
Additional Light Scatter Multiplier0 – 4 1Scales how strongly additional lights scatter in the smoke.
Additional Light Shadow Steps0 – 16 0Cheaper occlusion march for point/spot lights inside the smoke. 0 uses Shadow Steps. A value of 2 to 4 keeps the shading and removes most of the cost.
Depth Fade Distance0.0001 – 5 0.5Soft blend where the volume meets opaque geometry.
Flame Emission Tint / Flame Emission GainHDR colour / 0 – 8Colour and gain of the glow this fire casts on smoke through the compositor's Flame Emission. Does not change how the fire itself renders.

Shadow casting onto the scene

Volumes cast real-time shadows onto geometry through a proxy ShadowCaster pass. Shadow Alpha Threshold, Shadow Filter Radius, Shadow Cast Steps and Shadow Cast Density Scale shape that projected shadow. The cast is independent of the compositor, so it works with the fallback path too.

Core · URP

Scene Compositor

The NDVDB Composite renderer feature raymarches every visible volume jointly in a single pass, then resolves, upsamples and composites the result with correct depth so transparents and water sort against the smoke.

Why a joint march

Drawing each volume as its own transparent object breaks the moment two effects overlap, because a whole object must be either in front of or behind another. The compositor instead gathers up to eight visible volumes per frame and marches them together, so at every sample along a ray it knows the density of all of them. Overlap, interpenetration and cross-volume shadows fall out naturally. Volumes beyond the eight slots render through the per-volume fallback path, with a one-time warning in the Console.

What happens each frame

  1. Gather

    Every enabled player is collected once per frame and shared by every camera (Scene and Game views). The trace, resolve and composite passes are scissored to the screen rect the volumes actually cover, so a small effect on a big screen costs a small effect. Players entirely off screen still occupy a compositor slot and keep streaming; see the Performance Guide.

  2. Light cache

    Once per frame, a compute pass evaluates main-light self-shadow, sky occlusion and ground occlusion on each volume's brick lattice. The march then fetches instead of re-marching. See Lighting.

  3. Trace

    The joint march, at half resolution by default, writing colour with coverage plus a depth distribution (mean and spread) for the composite.

  4. Temporal resolve

    Accumulates with the previous frame's history per camera, rejecting samples that fall outside the current view to avoid smearing.

  5. Upsample and composite

    A joint-bilateral upsample to full resolution guided by scene depth, blended over the scene. A depth-occlusion write lets transparents and water that draw later sort against the smoke.

Quality settings

SettingDefaultMeaning
Step Size0.05World-space march step. The march adapts it to the occupied length along each ray, so this is a baseline, not a fixed cost.
Max Steps256Per-ray cap.
Soft Depth Fade0.15Soft blend against opaque geometry, in world units.
Half ResolutiononTrace at half resolution and upsample with depth guidance. Visually indistinguishable on most smoke, roughly a quarter of the cost.
Temporal Accumulation / Temporal Feedbackon / 0.88Blend with history to remove step noise. Higher feedback is smoother and slower to respond.

Shadow settings

SettingDefaultMeaning
Cross Volume ShadowsonOne volume's density darkens its neighbours along the light direction.
Cross Shadow Taps / Cross Shadow Strength4 / 1Samples per cross-shadow lookup and its weight.
Cross Shadow Point LightsonExtend cross-volume shadowing to additional lights.
Receive Scene ShadowsonGeometry shadows the smoke through the main-light shadow map. One filtered tap per march step.

Sorting with transparents

SettingDefaultMeaning
Transparents Depth OcclusiononWrite the smoke's representative depth so later transparents (particles, glass, water) sort against it.
Depth Occlusion Coverage0.5Coverage threshold at which the smoke counts as an occluder. Lower catches wispier smoke; higher keeps thin edges from hiding transparents behind them.
Injection PointBefore TransparentsWhere the composite runs. Water modes pin their own events and ignore this.

Performance settings

SettingDefaultMeaning
Light CacheonPer-volume cache of main-light self-shadow, sky occlusion and ground occlusion. Identical result, large saving on dense smoke. Light Cache Shader is found automatically.
Scissor To VolumesonRestrict every compositor pass to the screen rect covered by visible volumes.
Skip Overlay CamerasonOverlay cameras in a stack share the base camera's target. Compositing again would draw the smoke twice.

The Performance Guide explains what each of these costs and which to trade first.

Culling and the slot pool

The compositor has eight slots per frame, and a Surface Split volume takes two. With Frustum Culling on, only volumes whose bounds intersect the camera's view compete for them, so a scene can hold as many placed effects as the shot needs as long as no more than eight slots' worth are in view at once. The test uses a slightly wider frustum than the camera plus Cull Margin, so a volume is composited a step before its edge enters frame and its temporal history is primed.

Reveals never hitch. An off-screen player keeps streaming and keeps its GPU data current; it simply holds no slot and builds no light or emission cache while unseen. When the camera pans onto it, nothing has to be rebuilt. The player inspector shows "off screen" while this is the case.

When more than eight are in view, the player's Composite Priority decides who keeps a slot: higher wins, ties go to volumes already composited, then to the nearer one. The rest render through the per-volume path until the shot clears, and the Console says so once. Give a shot's hero effect a higher priority than its set dressing.

Pinning. A player's Always Composite keeps that volume in the compositor even while it is off screen: it always holds a slot and keeps its caches warm, so a camera moving faster than the reveal margin can never catch it between the per-volume path and the compositor. Pinned volumes win slot contention outright. It costs one slot while unseen, two for Surface Split, so reserve it for the hero effects of fast shots.

SettingDefaultMeaning
Frustum CullingonComposite only volumes in the camera's view. Per camera: the Scene and Game views each get their own set.
Cull Margin0.5World-space margin around each volume's box, on top of a 4% field-of-view allowance.
Composite Priority (player)0Slot priority when more volumes are in view than slots, -10 to 10.
Always Composite (player)offNever culled; always holds a slot and warm caches; first in line for contention.

Without the feature

If a renderer has no NDVDB Composite feature, each player draws itself through the per-volume path in the VDB Volume shader. You keep lighting, fire and shadow casting, but lose joint overlap, cross-volume shadows and water integration. The Setup window and the player inspector both flag this state.

Core

Lighting & Fire

How the smoke is lit, how it shadows itself and the scene, how the flame grid glows, and how fire can light the world around it.

Main light and self-shadow

The directional main light drives single scattering with a Henyey-Greenstein phase (see Anisotropy). Self-shadowing marches from each sample toward the light through the volume's own density, using Shadow Steps, Shadow Step Size and Shadow Density Scale from the material.

Light cache. With Light Cache enabled on the feature, that shadow march, the ambient sky occlusion and the ground-bounce occlusion are evaluated once per frame per volume on the brick lattice and stored in a small 3D texture. The march then does one fetch per sample. The result is identical; the saving is largest on dense, heavily shadowed smoke.

Scene shadows, both directions

  • Into the smoke. Receive Scene Shadows samples the main-light shadow map once per march step, so a building or a wing casts a real shadow through a plume.
  • Onto the scene. Each volume renders a proxy ShadowCaster pass so the plume darkens the ground under it. Shape it with the Shadow Cast … material properties.
  • Between volumes. Cross Volume Shadows lets one effect darken another along the light direction.

Point and spot lights

URP additional lights (point and spot) light the smoke through the standard light loop, up to Max Additional Lights Per Sample. Each light is occluded by the smoke between it and the sample. That occlusion is the expensive part, and Additional Light Shadow Steps on the material lets you make it cheaper than the main-light march without disabling it.

Flame grid

A flame grid emits blackbody colour scaled by Blackbody Intensity, above Flame Threshold with a Flame Falloff curve. If a temperature grid is mapped and Use Temperature Grid for Colour is on, temperature picks the colour along the ramp (up to Temperature Range Max, by default the grid's recorded maximum) while the flame grid keeps driving intensity. Flame Illum Radius spreads that glow into the surrounding smoke of the same volume, which is how a fireball lights its own soot from inside.

Fire as an area light

With Flame Emission enabled on the feature, the flame grid itself lights the smoke: this volume's own and every neighbour's. Once per frame, for each volume, a small irradiance cache is built by tracing twelve wide cones from every cache node through the brick-mean pyramids of all volumes, accumulating the fire's emitted radiance behind the smoke's own extinction. Exposed flame reaches the volume next door; buried flame is absorbed before it leaves. The march then adds one fetch per sample.

  • No hot spots and no lag. There are no point sources. The whole emitting surface contributes, and the cache is rebuilt from the current content frame, so the light moves exactly with the fire, holds when it holds, and dies when it dies.
  • Smooth by construction. Emission and occlusion come from trilinear brick-mean pyramids whose footprint widens with distance, and the lattice stores a field that is already band-limited to its own spacing. Fine structure in the image comes from the sharp density at each sample, not from the light.
  • The shine survives. Each node also stores the mean incoming direction and a directionality. The march blends its phase function from isotropic to Henyey-Greenstein accordingly, so smoke between you and the fire still glows more than smoke lit from behind you.
  • The crisp rim stays. Flame Illum Radius remains the sharp near-field term next to a flame tongue, below the cache's brick scale.
  • Colour is the material's. The fire's emission is evaluated per voxel with the material's flame threshold, falloff, flame ramp, blackbody tint and intensity, and temperature-driven colour when the material uses it, then averaged into the pyramid. A purple explosion lights its surroundings purple.
  • Creative colour per material. Flame Emission Tint and Flame Emission Gain on the volume material colour and scale the glow this fire casts on smoke without changing how the fire itself renders. A hot white core can cast a deep orange glow.
  • Water split volumes count once. A Surface Split player occupies two compositor slots; the emission pass treats them as one volume, so the fire never lights its own smoke twice.
Feature settingDefaultMeaning
Flame EmissionoffEnable the cone-traced fire light. Needs a flame grid on at least one volume. Dense and sparse-atlas volumes both work.
Flame Emission Strength1Overall gain, 0 to 8. 1 is the fire's own rendered radiance integrated over the sky as seen from the smoke.
Flame Emission Self0.1Weight of a volume's own fire on its own smoke, 0 to 4. Inside a fireball the smoke is lit from every direction, so 1 blows the source out toward white.
Flame Emission Neighbours1Weight of neighbouring volumes' fire on this smoke, 0 to 8. A neighbour sees the fire under a small solid angle, so this wants far more than Self.
Debug Flame Emission ViewoffPaints the cached irradiance times density instead of the shaded smoke, tone-compressed so hue stays readable at any Strength. Judge colour, smoothness and reach here first.
i

Division of labour. The emission cache lights smoke. Scene geometry is lit by the Flame Lights below, which are placed on exposed fire from the same data and skip the march while the cache is active.

Flame Lights: fire on geometry

Enable Emit Flame Lights on the player to spawn up to Flame Light Count real point lights on the fire. They are how fire reaches scene geometry: decks, walls, characters and water respond with their own albedo, specular and, if you enable it, shadows, which no screen-space trick can give in a forward renderer. Range, intensity, smoothing and flicker are per player. The lights animate in the sequence's own time, so a held frame holds them still, a scrub snaps them, and slow motion slows them.

Placed on visible fire. Each flame brick is weighted by how much of its light escapes the smoke around it, estimated from the density recorded at decode time. A light is earned, tracked and powered by that exposed fire: a blaze buried in its plume attracts no light, a light over fire that gets smothered fades in place, and a light never sits where you cannot see flame. Flame Light Exposure scales the effect; 0 restores raw flame brightness.

One job each. While Flame Emission lights the smoke, the flame lights are skipped inside the march and light geometry only, so fire never lights the smoke twice. Flame Lights Also Light Smoke on the feature restores the old behaviour. The material's Flame Emission Tint colours the lights as it colours the glow on smoke.

Player settingDefaultMeaning
Flame Light Count2Lights per player. Two to four covers most fireballs.
Flame Light Threshold0.25Minimum flame value for a cluster to attract a light.
Flame Light Intensity / Range100 / 0.75Light strength and reach (range is in volume space).
Flame Light Smoothing / Flicker0.4 / 0.15Temporal smoothing of position and intensity, and added flicker.
Flame Light Core Radius0Optional emitter core as a fraction of the light's range. Smoke inside it never occludes the light. Raise it per effect when a light is born deep in dense smoke; a large core also flattens the source volume's own shading, which is why it is off by default.
Flame Light Exposure1How strongly buried fire is discounted when earning, placing and powering lights. 1 uses the material's own absorption; 0 is raw flame brightness.

How flame lights are occluded. A flame light is shadowed by the smoke between the sample and the light, and by nothing behind it. The receiving volume's own density is marched only up to the light; neighbouring volumes' density is accumulated only along that same segment; the light's emitter core is excluded from both. Flame Light Occlusion Strength on the feature then scales the total (1 = as computed, 0.5 = half the optical depth, 0 = unoccluded).

Occlusion policy. Flame lights are recognised inside the march by position, so the feature's Flame Light Occlusion can treat them differently from scene lights:

PolicyBehaviour
Cheap Mean defaultOcclude with Flame Light Cheap Taps (default 4) reads of brick mean density. Looks right, costs little.
Same As Scene LightsFull per-light occlusion march.
SkipNo occlusion for flame lights. Cheapest; fine when the lights sit inside the fireball anyway.
✓

Flame lights barely reach nearby volumes? Lower Flame Light Occlusion Strength to 0.5 before raising intensity. Then raise Flame Light Core Radius if the light is born deep in a dense plume. Intensity mostly brightens geometry; occlusion is what decides whether the light gets out of the fireball.

✓

Keep flame lights optional. Most explosions read perfectly with the flame grid's own emission and illumination radius. Turn on flame lights when the fire must visibly light nearby geometry or a second volume.

Multiple scattering

Clouds

Clouds are almost pure scatterers, so the single biggest lever is the albedo you set through Smoke Absorption against Scatter Coefficient: the explosion default (8 against 4) is soot with an albedo of a third, which no amount of ambient will turn white. The shipped VDB_VolumeURP_Clouds material is a starting point: absorption 0.3, scatter 12, Anisotropy 0.55 for the silver lining, Multiple Scattering 2, ambient 0.35 in a sky blue with Ambient Sky Occlusion 0.5. Tune Density Multiplier to the asset first, then step size, then the rest.

Matching a reference render. Cloud references are lit by a whole sky and a ground, not one colour. Set Ambient Source to Scene Ambient with the reference's HDRI as the skybox: the sky term takes the probe's colour straight up and Ground Bounce (start at 0.5) the colour straight down, both occluded through the cloud, so the shading follows the skybox instead of two hand-picked colours; bring Ambient Intensity up to about 1. Add Powder (0.3 to 0.6) for the darker front-lit crevices of real cumulus. Neither costs a march when the light cache is on.

Multiple Scattering approximates the light that bounces around inside the volume before reaching the camera. It brightens dense regions toward the smoke's albedo without changing their opacity, so you can have bright white steam and pyro next to dark soot using the same density.

Integration · URP

Water Integration

Explosions at the waterline are the shots everyone remembers. NDVDB splits the march at the rendered water surface so smoke renders correctly above, below, and through it, from either side of the surface.

Water Compositing modes

Set per effect on the NDVDB Player.

ModeUse whenWhat it does
Depth Sorted defaultThe effect never touches water, or sits fully on one side.Sorts against water by depth like any transparent. Cheapest.
Show AboveThe effect is above the surface but its bounds dip below it.Marches only the part above the water and presents it on the correct side of the water pass.
Show BelowThe effect is underwater.Marches only the part below the water; the water's own refraction distorts it.
Surface SplitBreaches: launches, impacts, dives.Marches both halves and presents each on its side. The most complete, and the most expensive: it uses two of the compositor's eight slots.

Water detection (no component needed)

With Auto Detect Water on the feature (default), NDVDB finds KWS Water System 2 and Crest Water 4 in the scene through reflection. There is no compile dependency and nothing to add. Detection provides:

  • the mean water height and whether the camera is above, below or straddling the surface;
  • the water system's own per-pixel surface mask (the KWS2 prepass, the Crest ocean mask), so every pixel knows which side it is on;
  • the water system's surface depth, used for the wave-exact split and wave occlusion.

For any other planar water, add an NDVDB Water Surface component at the water height. It supplies the plane; the split then uses a plane test per pixel.

ComponentPurpose
NDVDB Water SurfaceGeneric planar water. Height Offset shifts the plane. Split Mask Source: Auto, Plane Test, or Water System Mask.
NDVDB Water Surface KWS / … CrestOptional overrides when auto-detection is on. Use them to force a height offset or a mask source for that system.

The wave-exact split

With Wave Exact Split (default on, active when a water system mask is available) each ray is clipped where it crosses the rendered water surface rather than a flat plane at the mean height. The above-half stops at the wave; the below-half starts there. Because the surface is read from the water system's own depth prepass, the split follows every wave point for point, and it stays correct when:

  • the camera sits exactly on the waterline with the view split across it;
  • an effect breaches the surface, in either direction;
  • the camera crosses the surface during a shot. Each half keeps its own temporal history, so there is no flash;
  • the camera is fully submerged below the troughs, looking up at crests. The post-water composite occludes at the same per-pixel surface crossing the march clipped at, so clip and presentation always agree.

Presentation. The half on the far side of the water from the camera is drawn before the water, into the opaque capture the water refracts. The half on the camera's side is composited after the water. A camera clear of the surface reduces to one side per half; a straddling camera gets both on one screen, decided per pixel by the mask. With any water source the far-side halves are drawn after the skybox, so they keep their soft edges through the surface, and with KWS2 or Crest the underwater half seen from above also carries a fog-matched depth for the surface's own depth fog (Crest's exponential fog, KWS2's absorption law): thin smoke fogs like the water behind it, dense smoke at its own distance, and the effect stays visible through deep water instead of being fogged to the seabed.

Plane mode

Without a water-system mask (generic NDVDB Water Surface) the split is a plane at the mean height. Wave Crest Compensation lowers that plane by roughly your wave amplitude so crests do not cut the above-water smoke. It has no effect in wave-exact mode, where it is unnecessary.

Underwater fog

Match Water Fog (default on) makes underwater smoke fog exactly like everything else in the water. With KWS2 the compositor applies KWS2's absorption law to the smoke. With Crest, whose underwater pass runs after the compositor and fogs each pixel at the depth it finds, the compositor presents the smoke with a fog-matched depth: a per-pixel depth chosen so that Crest's own fog, in Crest's own colour, yields exactly smoke fogged at its distance over water fogged at its own. No fog colour is ported, so edges are continuous whatever the ocean material does, and only Crest's depth fog density is read. Because Crest reads URP's depth texture rather than the live depth buffer, that depth is mirrored into the depth texture as well.

Far Side Distortion Range (default 0) is an advanced lever for the far-side half: beyond this distance the half is presented with depth cleared so the water's distortion covers it. Leave it at 0 unless you see a seam in the water system's own refraction at range.

Diagnostics

Feature settingWhat it shows
Log Water RoutingOne-shot log line per camera: detected source (KWS2, Crest, component, none), side mode, camera above/below/split, and which halves were enqueued. Auto-unticks.
Debug Water Side View → Pixel SidePaints the per-pixel decision instead of the smoke: red classed underwater, green above, blue the raw mask. Use while the camera straddles the surface.

Checklist for a waterline shot

  1. Player: Surface Split

    Set Water Compositing to Surface Split on the effect that breaches. Effects fully on one side can stay Depth Sorted.

  2. Feature: defaults

    Auto Detect Water, Wave Exact Split and Match Water Fog on. No component needed for KWS2 or Crest.

  3. Verify routing

    Tick Log Water Routing once and confirm the source and side mode in the Console.

  4. Look at the seam

    Put the camera on the waterline. If a half is missing, switch the debug view to Pixel Side and check the classification matches the picture.

Integration

Timeline

A native Timeline track binds to an NDVDB Player. Clips start, loop, time-remap and scrub the sequence, in the editor and in Play mode.

Adding a track

  1. Add an NDVDB track

    In the Timeline window, Add ▸ NDVDB Timeline Track. Bind it to the NDVDB Player in the scene.

  2. Add clips

    Right-click the track and add an NDVDB clip. Move and trim it like any clip; the sequence plays from its first frame at the clip start.

  3. Scrub

    Drag the playhead. The player seeks to the matching frame and the Scene view updates without Play mode.

  4. Check the timeline's length

    In the Timeline settings keep Duration Mode at Based On Clips, or make sure a Fixed Length timeline's end marker sits past your clips. A fixed length of 0 plays for zero seconds in Play mode even though scrubbing still works.

Clip settings

A new clip snaps to the sequence's exact length: frame count divided by the frame rate in effect. Change the clip's Frame Rate Override and the clip rescales in place, taking the clip's speed multiplier into account, so the timeline always shows the true duration of what will play.

SettingDefaultMeaning
Loop ClipoffWrap the sequence inside the clip. Off holds the last frame when the clip outlasts the sequence.
Frame Rate Override0Play the sequence at this rate inside the clip and resize the clip to match. 0 uses the asset's frame rate. Combine with clip speed for slow motion.

Clips support clip-in, speed multiplier, looping and extrapolation, so the standard Timeline trimming and retiming tools all apply.

Activation and memory

A player with no active clip on its track is idle: it holds its last frame or, with an Activation track controlling the GameObject, is disabled and releases its cache. For long cinematics with many effects, pair NDVDB tracks with Activation tracks so only the effects in the current shot occupy RAM and GPU atlas slots.

Timeline and Layout Mode

When a Timeline is playing, the editor's Layout Mode Auto level drops all proxies and renders at full fidelity. Scrubbing a stopped Timeline keeps the proxies, so blocking a shot stays fast. See Layout Mode.

Integration · Editor only

Layout Mode

Block a VDB-heavy cinematic with cheap proxies, then get full fidelity the moment anything plays. Layout Mode never touches Play mode, recording or builds.

Levels

LevelWhat rendersCost
Off (Full)Everything at full quality.Full
PreviewThe real volume at a coarser march with lighting simplified. Reads correctly for framing and timing.Low
BrickOccupied bricks as a tinted block silhouette. Shows the sim's shape and extent per frame.Very low
Bounds OnlyA tinted box gizmo of the volume bounds. Scrubbing seeks are deferred until you leave this level.Near zero
Auto (full while playing)Applies the chosen level while idle and drops to Full whenever a player or Timeline is playing.—

Where to set it

  • Scene view overlay. The NDVDB overlay in the Scene view toolbar switches the session-wide level with one click. The same overlay carries the compositor and player sub-windows described below.
  • Menu. Tools ▸ NDVDB ▸ Layout Mode ▸ Off / Preview / Brick / Bounds Only / Auto.
  • Per player. Proxy Mode on the NDVDB Player: Inherit the session level, or pin this effect to Full, Preview, Brick or Bounds Only. Pin the hero effect to Full and let the background effects follow the session.

The level is stored in the editor session, not in the scene, so it never ships and never dirties your assets. The player inspector shows the effective level and a warning when a proxy is active.

i

Guaranteed full fidelity. Entering Play mode, Timeline playback, Recorder sessions and builds always resolve to Full regardless of the session level.

The NDVDB overlay

One overlay in the Scene view carries everything you need while working: docked in the toolbar it is a compact strip, floated or docked to a side it expands into a dashboard with the same three sections.

  • Layout. The level buttons and the Auto toggle.
  • Compositor (settings icon). A live status line, "N of 8 slots, M in view, K off screen, tier", plus the feature's day-to-day levers without leaving the Scene view: Frustum Culling and Light Cache; Quality with Half Resolution, Temporal Accumulation, Temporal Feedback and Step Size; Flame Emission with its Strength, Self and Neighbours sliders; Water with Match Water Fog, Wave Exact Split and Wave Crest Compensation; the two debug views and a one-shot water routing log. Every change is undoable, one undo step per drag, and saved on the renderer feature.
  • Players (memory icon; the button itself shows slots in use and the off-screen count). A table of every player in the open scenes: state (composited, off screen, per-volume, proxy, disabled), current frame, frame-cache RAM, GPU memory, and a Pin toggle for Always Composite. Click a name to select the player. Totals at the bottom.

The footer opens Setup, the Converter, and this documentation.

Ship it

Performance Guide

What costs what, which levers lose nothing, and which to trade first when a shot is over budget.

Free wins (on by default)

  • Light Cache. Main-light self-shadow, sky and ground occlusion once per frame per volume instead of per sample. Identical image.
  • Scissor To Volumes. Every compositor pass is limited to the screen rect the volumes cover. A small effect on a big screen costs a small effect.
  • Shared gather. Volume collection and uploads happen once per frame and are shared by the Scene and Game views and any extra cameras.
  • Empty space skip and sparse atlas. The march walks occupancy and skips empty bricks; only occupied bricks live in GPU memory.
  • Skip Overlay Cameras. Camera stacks composite once.

Flame Emission is opt-in. Its cost is one compute build per frame, about a 32³ lattice times twelve cones times up to ten steps per marched volume, and one texture fetch per sample in the march. On a desktop GPU that is a fraction of a millisecond per volume. It replaces the need for Flame Lights on smoke, so turning those off when it is on usually nets a saving.

What to trade, in order

  1. Half Resolution + Temporal Accumulation

    Keep both on. This is the single largest saving and the temporal resolve hides the half-res trace on smoke.

  2. Additional Light Shadow Steps

    Set 2 to 4 on the material. Point and spot light occlusion drops from the main-light shadow cost to a fraction, with the same shading.

  3. Flame Light Occlusion → Cheap Mean

    The default. Use Skip when the flame lights sit inside the fireball.

  4. Step Size

    Raise gradually. The march adapts to occupied length, so a larger baseline mostly affects long rays through thin smoke.

  5. Cross Shadow Taps

    Drop from 4 to 2 on scenes where volumes rarely overlap along the light.

  6. Max Additional Lights Per Sample

    Lower it when many small lights sit near the smoke.

  7. Density Multiplier for thin sims

    A trail that needs Density Multiplier 40 to read is also a trail that terminates rays sooner. Thicker is often cheaper.

Memory

  • RAM. Max Cache Ram MB caps decoded frames. The Frame Cache section of the player shows use. Sequences that fit stay resident.
  • VRAM. Each enabled player holds an atlas slot sized to its densest frame. Disabled players hold nothing.
  • Off-screen players take no compositor slot and build no caches, but keep their frame cache and GPU data current so they can enter frame without a hitch. Use Timeline Activation or disable them to release the memory itself.

A/B toggles

The feature's diagnostics let you measure a suspect cost without editing materials:

ToggleRemoves
Debug Skip Additional Lights In MarchAll point/spot lighting inside the smoke.
Debug Skip Add Light Self ShadowOnly the per-light occlusion march; lights still shade.

If the frame time barely moves with a toggle, that path is not your problem.

Rules of thumb

Screen coverage dominates

Cost scales with the pixels the smoke covers, not with how many volumes exist. A full-screen plume costs more than eight small ones.

Overlap is cheap

The joint march pays per ray, not per volume. Overlapping effects add samples, not passes.

Lights are per sample

Every additional light is evaluated at every sample it reaches. Range matters more than count.

Ship it

Builds & Deployment

Sequences deploy themselves. The build processor copies every referenced .ndvdb into StreamingAssets for Windows, Linux and macOS standalone players.

How it works

  • Each NDVDB Asset references its .ndvdb file. After the build, the processor copies every sequence referenced by an enabled build scene straight into the finished build's StreamingAssets/NDVDB folder, where the runtime streams from.
  • Nothing is staged through Assets/StreamingAssets. That keeps the project from doubling hundreds of megabytes per sequence and keeps the asset database from hashing them during the build.
  • Unused sequences are not copied, so they never bloat the build.
  • The compositor's compute shaders live in Resources inside the package and are included automatically.
!

Runtime-instantiated prefabs. A sequence used only by a prefab that appears in no build scene is not detected. Copy its .ndvdb into the build's StreamingAssets/NDVDB folder yourself, or reference it from a scene.

Platform notes

PlatformAPIStatus
WindowsD3D12, VulkanSupported
LinuxVulkanSupported
macOSMetalSupported
Mobile, console, WebGPU—Not supported in this release

Pre-ship checklist

  1. Run NDVDB Setup

    Every renderer used by a shipped camera needs the compositor feature and the depth texture.

  2. Set a RAM budget

    The default of 0 is unlimited, which is right for a few sequences. When many large sequences play together, give each player a Max Cache Ram MB that fits the minimum-spec machine.

  3. Layout Mode is not a concern

    It is editor-only session state and never ships.

  4. Test a build

    Confirm the sequences appear under StreamingAssets and play from a clean install path.

Ship it

Troubleshooting

Start with Tools ▸ NDVDB ▸ Setup. It catches the three causes of "nothing renders" and fixes them with one click. If a problem survives this page, reach us at ndimensionalgames.com/customer-support with the Console log, your Unity and URP versions, and the exporter the VDB came from.

Nothing renders, or the volume is a flat box
  • URP is not the active pipeline. Assign a URP asset in Project Settings ▸ Graphics.
  • The renderer has no NDVDB Composite feature. Run Setup, or check Playback Readiness on the player.
  • The depth texture is off. Setup's Enable Depth Texture fixes it.
  • Layout Mode is at Bounds Only or Brick. Check the Scene view overlay.
The volume plays when you scrub the Timeline but not in Play mode

Open the Timeline window's settings (the cog). If Duration Mode is Fixed Length and the length reads 0, the PlayableDirector sees a zero-second timeline: it ends on its first frame in Play mode, pauses and drops its graph, while scrubbing in the window still evaluates the clips. Choose Based On Clips, or drag the timeline's end marker past your clips. The NDVDB track logs a warning naming the timeline when it detects this. A player that is driven by Timeline also shows this in Play mode as a Current Frame that never leaves 0. Turn on Tools ▸ NDVDB ▸ Diagnostics ▸ Play-Mode Trace to get one Console line per second with what the timeline asked for, what the player applied and the director's state.

The material keeps reverting to the default explosion look

With Instance Material on, the player clones its Material Template. A template set on a prefab instance now wins over the material still sitting on the prefab's MeshRenderer; assigning a material on the renderer instead re-clones from that one. Both routes work, the most recent change wins.

The smoke is drawn twice or looks doubled

A camera stack with an Overlay camera that also composites. Skip Overlay Cameras is on by default; if you disabled it, re-enable it.

Particles or glass draw on top of the smoke they should be behind

Enable Transparents Depth Occlusion and lower Depth Occlusion Coverage so wispier smoke counts as an occluder. Also confirm the transparent material writes and tests depth as expected.

A Houdini or Maya export lies on its side

Those applications are Y-up. Re-convert with Source Up Axis on Auto (it reads the Houdini creator tag) or Y-up, or rotate the GameObject −90° about X for an asset already converted as Z-up.

The sun shines straight through dense smoke with some skyboxes

Suns baked into HDRI cubemaps sit at tens of thousands of nits, and any residual transmittance multiplies that. The march used to stop at 0.2% transmittance and leave that floor in place: invisible against a procedural sky of a few units, a white disc against an HDRI. The exit threshold now maps to exactly opaque, so dense smoke blocks any background. Through thin smoke a very bright sun still shows, which is what a camera sees; if that reads as a leak in your shot, lower the cubemap's sun or drive the sun from the directional light. Lens flare components are a separate case: they draw after the compositor and are hidden only by depth, so enable Transparents Depth Occlusion and check the flare's occlusion settings.

Underwater smoke is invisible, or only its densest blotches show, from above the water (KWS2, Crest)

The water surface fogs what it refracts by the depth behind the surface. Leave Match Water Fog on: the compositor then presents the underwater half with a fog-matched depth, and the surface fogs it at the smoke's own distance. With it off, the smoke is still in the refraction but fogged as if it sat on the seabed, which in deep water hides it completely. Log Water Routing prints one summary line per camera; it should read sideMode=2 (KWS2) or 3 (Crest), farEvent=AfterRenderingSkybox+1 and farSurfaceFog=True.

A water half is missing, or the split is a flat line
  • Tick Log Water Routing and read the Console line. Source none means neither KWS2 nor Crest was detected and no NDVDB Water Surface exists.
  • Side mode 1 (plane) with KWS2 or Crest present means the water system's mask was not available this frame. Confirm the water is enabled and rendering for that camera.
  • Switch Debug Water Side View to Pixel Side. The red/green picture must match the water you see.
  • The player's Water Compositing must be Surface Split for a breach. Show Above or Show Below intentionally hide the other half.
Underwater smoke looks like a cutout in the fog, or a lighter halo surrounds it

Enable Match Water Fog on the feature and keep Transparents Depth Occlusion on. For Crest the fog port also needs an Ocean Renderer whose material carries the depth fog density; the routing log names the detected source.

Ghosting or smearing when the camera moves fast

Lower Temporal Feedback (try 0.8). The resolve already rejects history outside the current view; feedback controls how quickly the rest converges.

The volume's shadow on the ground is a soft blob, whatever the material says

The shadow is drawn into URP's shadow map, so its sharpness is set by the URP asset before any material control. Shadow Distance decides the texel size: with a 2048 map and four cascades, 1000 m puts the first cascade at roughly 12 cm per texel, while 100 m gives about 1 cm. Lower the distance, or move the first cascade split in, and raise Main Light Shadow Resolution to 4096 if you need more. Keep Soft Shadows on: its filtering is what blends the brick boundaries of the caster into a continuous shadow; only lower its quality if the penumbra is wider than you want. On the volume material, Shadow Alpha Threshold maps alpha from the threshold to five times the threshold onto shadow strength, so a lower value (0.05 to 0.1) reads as a solid plume rather than a haze, and Shadow Cast Density Scale darkens the footprint.

Flame lights blow out nearby surfaces, linger, or wander off the fire

Flame lights are real point lights, so a surface a metre from the fire sees the full inverse-square intensity. On the player's Flame Lights section: Intensity defaults to 100, tuned for a large fireball lighting distant smoke, so 30 to 40 suits fire near geometry; Range is a multiple of the effect's longest axis, and 0.4 to 0.5 keeps the lit pool under the fire; Smoothing is the settle time for brightness and position, so lower it to 0.1 to 0.15 when lights outlive the flame; Threshold decides which regions earn a light, with hysteresis down to half its value before the light dies, so 0.4 to 0.5 keeps lights on the core only. Leave Exposure at 1, which places lights on flame that escapes the smoke, and Core Radius at 0 unless the fire must light a neighbouring volume through its own plume. A Bloom post-process volume multiplies whatever hot spot remains.

Fire does not light nearby smoke, or lights it too much

Enable Flame Emission on the feature and look at Debug Flame Emission View first. If the field is empty, read the "[NDVDB] Flame Emission" line in the Console: it lists, per slot, whether a flame source was found, the proxy state, and whether the compositor pass has compiled. If neighbours are weak, raise Flame Emission Neighbours; if the source volume looks washed out, lower Flame Emission Self. Flame Lights can stay off for smoke once emission is on.

Flame lights barely light nearby volumes even at high intensity

Occlusion, not intensity, is the limiter. Lower the feature's Flame Light Occlusion Strength (try 0.5), then raise the player's Flame Light Core Radius if the light spawns deep inside dense smoke. Confirm Flame Light Occlusion is not set to Skip if you expect soft occlusion, and that Cross Shadow Point Lights is on if you want neighbours to occlude at all.

Flame lights stopped lighting the smoke after enabling Flame Emission

By design: the emission cache lights the smoke and the lights light geometry, so fire is not counted twice. Tick Flame Lights Also Light Smoke on the feature to have both.

Flame lights jump on held frames or during scrubbing

They animate in content time, so a held frame keeps them still by design. If they still jump, raise Flame Light Smoothing or lower Flame Light Count so clusters do not swap.

Water-system console errors right after a shader recompile

KWS2 logs transient compute-shader property and kernel errors during a domain reload. They clear on the next frame and are unrelated to NDVDB. If they persist across frames, re-import the KWS2 folder.

The effect drops into the scene 100× too big or too small

The file's units do not match Unity's metres. Set Source Units in the converter (Centimetres for Unreal-targeted exports and for EmberGen sims built around a centimetre FBX imported at 100%), or fix an asset you already converted with Import Transform → Scale in its inspector (0.01 for centimetres): the header is patched in place, no re-conversion. Unity's FBX importer converts centimetre files to metres and EmberGen does not, which is where the 100× comes from; importing the FBX into EmberGen at 1% makes new sims metre-true.

The converted asset flickers between two effects, or has more frames than the export

The source folder holds more than one numbered sequence, or a stray .vdb sits beside the frames. The converter groups files by name and shows a Sequence dropdown under the folder when that happens; check the pick (the largest sequence is selected by default) and the frame count under it before converting. Older assets converted from such a folder need a re-conversion.

Conversion rejects my VDB

Only standard OpenVDB float grids are accepted. Export float grids (not half or double) and untiled storage from your DCC. In Houdini, set the VDB's storage to 32-bit float before writing.

The effect is too faint even at high density

Density Multiplier goes to 100. For very thin sims (trails, steam), values of 20 to 60 are normal. Lower Density Bias if the sim's values are small overall.

Reference

Settings Reference

Every inspector setting in one place, grouped by component. Defaults in grey.

NDVDB Composite (renderer feature)

GroupSettingDefaultNotes
QualityStep Size0.050.005 – 1, world units
Max Steps25632 – 1024
Soft Depth Fade0.150 – 2
ShadowsCross Volume Shadowson
Cross Shadow Taps42 – 8
Cross Shadow Strength10 – 4
Receive Scene Shadows / Cross Shadow Point Lightson / on
CullingFrustum CullingonPer camera
Cull Margin0.50 – 10 m
PerformanceHalf Resolutionon
Temporal Accumulation / Temporal Feedbackon / 0.880 – 0.95
Light Cache / Scissor To Volumes / Skip Overlay CamerasonIdentical image, lower cost
SortingTransparents Depth Occlusionon
Depth Occlusion Coverage0.50.05 – 0.95
WaterAuto Detect WateronKWS2, Crest
Wave Exact SplitonNeeds a water-system mask
Wave Crest Compensation00 – 5, plane mode only
Match Water Fogon
Far Side Distortion Range00 – 200, advanced
Flame emissionFlame EmissionoffCone-traced fire light on smoke
Flame Emission Strength10 – 8
Flame Emission Self0.10 – 4
Flame Emission Neighbours10 – 8
Debug Flame Emission ViewoffPaints the irradiance field
Flame Lights Also Light SmokeoffLights skip the march while emission is active
Flame lightsFlame Light OcclusionCheap MeanSame As Scene Lights · Cheap Mean · Skip
Flame Light Cheap Taps42 – 8
Flame Light Occlusion Strength10 – 2, exponent on the transmittance
DiagnosticsLog Water RoutingoffOne-shot
Debug Water Side ViewOffOff · Pixel Side
Debug Skip Additional Lights In March / Debug Skip Add Light Self ShadowoffA/B only
AdvancedInjection PointBefore Rendering TransparentsWater modes ignore it

NDVDB Player

GroupSettingDefault
PlaybackNdvdb Asset—
Frame Rate24
Loopon
Play On Start / Play On Enableon / off
MaterialMaterial Template—
Instance Materialon
DataUse Sparse Atlason
Enable Empty Space Skipon
Stream Lookahead16
Max Cache Ram MB0 (unlimited)
PlacementUse VDB Originoff
Fit Natural World Sizeoff
Auto Depth Sorton
WaterWater CompositingDepth Sorted
Underwater Materialoptional
Composite Priority0
Always Compositeoff
Flame lightsEmit Flame Lightsoff
Flame Light Count2
Flame Light Threshold0.25
Flame Light Intensity / Range100 / 0.75
Flame Light Smoothing0.4
Flame Light Flicker0.15
Flame Light Core Radius0
Flame Light Exposure1
EmissionOverride Emission Propertiesoff
Blackbody Tint / Blackbody Intensitywhite / 15
LayoutProxy ModeInherit

VDB Volume material

See Volume Material for the grouped tables with ranges and guidance.

Water components

ComponentSettingDefault
NDVDB Water SurfaceHeight Offset0
Split Mask SourceAuto (Plane Test · Water System Mask)
NDVDB Water Surface KWS / CrestSame, as overrides for auto-detected systems

Timeline clip

SettingDefault
Loop Clipoff
Frame Rate Override0 (player rate)

Menus

MenuOpens
Tools ▸ NDVDB ▸ SetupProject setup window with one-click fixes
Tools ▸ NDVDB ▸ ConverterThe converter
Tools ▸ NDVDB ▸ Layout Mode ▸ …Session-wide proxy level
Tools ▸ NDVDB ▸ Diagnostics ▸ Play-Mode TracePer-second Console trace of playback, Timeline drive and compositor state
Tools ▸ NDVDB ▸ DocumentationThis documentation, in your browser
Reference

The .ndvdb Format

A streaming container designed for GPU upload: sparse bricks, per-brick quantization, temporal deltas, and a lossless Zstandard envelope.

Layout

Bricks
8³ voxels. Only occupied bricks are stored. An indirection table maps volume space to atlas slots on the GPU.
Quantization
8 bits per voxel with a per-brick min/max range. Effective precision is roughly 11 to 12 bits because each brick uses its own range.
Temporal coding
I-frames store all occupied bricks. P-frames store bricks that changed beyond the tier's tolerance. The Cinematic tier keeps the tolerance near lossless and inserts I-frames periodically for fast seeks.
Envelope
Zstandard level 1, lossless: on brick data it lands within a few percent of the slowest levels at several times the speed. Draft skips packing entirely.
Grids
Density, flame and optional temperature, each with its own brick set and role recorded in the header.
Metadata
Voxel size, world bounds and simulation origin from the VDB, frame count and frame rate hint, each grid's maximum value (the temperature ramp's default range), per-brick means used by the light cache and cheap occlusion paths.

Streaming behaviour

  • An IO thread reads and decodes Stream Lookahead frames ahead of the playhead into the frame cache.
  • The GPU receives only bricks that changed since the previously uploaded frame; the atlas apron (the one-voxel border that keeps trilinear filtering seamless across bricks) is refilled incrementally for touched bricks.
  • Seeking to a frame decodes from the nearest previous I-frame, which is why the Cinematic tier's periodic I-frames matter for scrubbing.

Metadata and the asset inspector

Selecting an NDVDB Asset shows the file's metadata: path, frame count and duration, value range, compressed and uncompressed sizes with the compression ratio, average frame size, source VDB and creator. Two things are editable there. Import Transform: a uniform scale about the simulation origin and a translation, applied to the bounds and voxel size when the file is read, so 0.01 turns a centimetre export into metres and an offset re-centres a domain-local cache; rotation belongs on the GameObject. The frame rate: the new value is patched into the .ndvdb header in place, losslessly and instantly, with no re-conversion, and every player and Timeline clip using the asset picks it up.

Compatibility

The container carries a format version in its header, at a fixed offset that has never moved. The reader checks it before reading anything version-dependent, so assets converted with an earlier NDVDB continue to load. Re-convert with the current version when you want the newest encoding.

Reference

FAQ & Limits

How many volumes can be on screen?

Eight compositor slots per frame, in view at once; a volume in Surface Split water mode uses two. Because off-screen volumes are culled from the slots, a scene can hold any number of placed effects. When more than eight slots' worth are in view, Composite Priority on the player decides who keeps a slot and the rest render through the per-volume path, with a one-time Console warning. The shader compiles a 4-slot variant for the common case and switches to the 8-slot variant only when more are gathered.

Does it work with HDRP?

An experimental HDRP subshader is included for per-volume playback. The compositor and water integration are URP-only, and URP is the supported pipeline.

Does it work with Built-in?

No. NDVDB requires URP.

Can I edit or simulate volumes at runtime?

No. Volumes are streamed effects authored in your DCC. You can move, scale, time-remap, light and composite them at runtime.

Which water systems are detected?

KWS Water System 2 and Crest Water 4 (URP), through reflection with no compile dependency. Any other planar water works with the generic NDVDB Water Surface component.

Does Layout Mode affect builds or recordings?

No. It is editor session state. Play mode, Timeline playback, Recorder and builds always render at full fidelity.

Mobile or console?

Not supported in this release. The compositor needs compute and a desktop-class GPU.

Is the source included?

Yes. Full C# and shader source, with assembly definitions for Runtime and Editor. One-time purchase, no subscription.

Where do I get support?

At ndimensionalgames.com/customer-support. Include the Console log, your Unity and URP versions, the water system if one is involved, and where the VDB was exported from. For conversion problems the Console's Metadata scan and Profile lines from the converter tell most of the story.

Third-party components?

The package redistributes permissively licensed open-source components (Apache-2.0 / BSD / MIT). Full notices are included in the package.

NDVDB - Cinematic OpenVDB Importer & Real-Time Renderer · © N Dimensional Games LLC. Documentation for version 1.0. · Support