Goodnight Wiki / Volumetric Rendering

Volumetric Rendering

Most of computer graphics is about surfaces. Triangles, pixels, BRDFs — the whole machinery assumes that light bounces off things with well-defined boundaries. Volumetric rendering is what happens when you need to draw clouds, smoke, fog, fire — things that don't have surfaces. Light enters a volume, scatters, absorbs, and emerges from the other side diminished and redirected. It's fundamentally different from surface rendering, and it's also fundamentally more expensive.

Ray Marching Through Volumes

The basic algorithm is ray marching: shoot a ray from the camera, step through the volume at regular intervals, and at each step sample the density and accumulate light absorption.1 If the density at a point is sigma, then transmittance through a slab of thickness dt is approximately 1 - sigma * dt. Multiply these together along the ray and you get the total transmittance — how much background light makes it through the volume to your eye.

What makes this practical for real-time work is that you don't need to simulate every photon interaction. For rendering (as opposed to simulation), a single-scattering model where light enters the volume, gets partially absorbed, and reaches the camera is often good enough. The expensive part is the number of steps: too few and you get banding artifacts (visible layers where the sampling caught a density boundary), too many and you blow your frame budget.

The trick used by Chris Wallis and others in ShaderToy volumetrics is to model the volume using Signed Distance Functions with fBM noise distortion.1 Start with a few smooth-unioned spheres for the basic shape, then add fractal Brownian motion noise indexed by position to roughen the surface. The SDF gives you an implicit density field — distance from the surface maps to density — and you can ray march through it with standard techniques. Animating the noise offset over time gives the rolling, billowing motion of clouds and fog.

The visual difference between "opaque SDF shape" and "volumetric SDF shape" is dramatic. The opaque version looks like a weird blob; the volumetric version, with light accumulation along the ray, looks like a cloud. The physical intuition is that clouds aren't solid — they're gradients of water droplet density — and volumetric rendering captures this gradient where surface rendering cannot.

OpenVDB: Sparse Volumes at Scale

The industrial-grade solution to volumetric data storage is OpenVDB, developed at DreamWorks Animation by Ken Museth and now maintained by the Academy Software Foundation.2 VDB is a hierarchical data structure for sparse volumes — it stores voxel data only where it exists, using a tree of progressively finer grids. An effectively infinite 3D index space is compressed down to whatever the actual data occupies.

The key insight is that most volumetric data is sparse. A cloud in an otherwise empty sky, fire rising from a localized source, water splashing from a specific impact — in all cases, the interesting data occupies a tiny fraction of the bounding volume. A dense 3D grid wastes enormous amounts of memory on empty space. VDB's tree structure (typically 3-4 levels deep, with the finest level being 8x8x8 voxel blocks) lets you skip empty regions entirely during both storage and traversal.2

The data structure supports both level sets (signed distance fields where the surface is the zero crossing) and fog volumes (density fields where every voxel has a value). Level sets are used for water surfaces and solid object boundaries; fog volumes are used for clouds, smoke, and fire. DreamWorks' cloud modeling pipeline — the system behind clouds in films like How to Train Your Dragon — is built on VDB's filtering and morphological operations.

NanoVDB: Volumes on the GPU

OpenVDB was designed for CPUs. Its tree nodes use pointers, it depends on several external C++ libraries, and it supports dynamic topology modification — all things that don't play well with GPU architectures. NanoVDB, developed at NVIDIA, solves this by creating a linearized, read-only representation of the VDB tree that replaces pointers with indices.3

The result is a flat buffer that can be uploaded to GPU memory with a single cudaMemcpy and traversed in CUDA kernels or even graphics shader code. The linearization preserves the tree hierarchy — you still get the O(1) access time and empty-space skipping of the full VDB — but in a format that works on massively parallel hardware.

For rendering, the spatial coherency of rays stepping through a volume is exploited via a ReadAccessor that caches the tree traversal stack. As a ray advances, it's likely to stay in the same or neighboring tree nodes, so bottom-up traversal from the cache is much faster than top-down traversal from the root every time. This is combined with a Hierarchical Digital Differential Analyzer (HDDA) that uses the tree structure itself for empty-space skipping — if an entire tree node is empty, the ray jumps past it in one step rather than stepping through every voxel.3

The practical impact is that volumetric rendering workflows that previously required offline computation can now run in real-time or near-real-time. Level set ray intersection (finding where a ray crosses the zero-crossing surface) is fast enough for interactive viewport rendering. Transmittance computation for fog volumes runs at real-time rates for moderate step sizes. And collision detection against volumetric surfaces — where you need signed distance lookups at particle positions — maps naturally to the NanoVDB access pattern.3

The Cost Tradeoff

Volumetric rendering remains expensive. Every pixel that sees a volume requires dozens or hundreds of ray march steps, each involving a texture lookup or tree traversal. Compare this to a surface pixel that needs one material evaluation, and you can see why even modern games fake most of their "volumetric" effects with billboards, screen-space tricks, or precomputed light transport.

The honest approach — the one taken by film pipelines — is to accept the cost and throw compute at it. OpenVDB/NanoVDB plus path-traced volumetric scattering will give you physically accurate clouds and fire. But the gap between film and game volumetrics remains one of the widest in graphics: where Path Tracing of surfaces has become real-time (via hardware RT cores), path-tracing of volumes is still orders of magnitude too expensive for 60fps rendering. The Fluid Simulation article covers the dynamics side — how to generate the density fields themselves. Here we're concerned with how to turn those fields into pixels, and the answer is: carefully, expensively, and with as much spatial coherency exploitation as your data structure allows.

Footnotes

  1. Volumetric Rendering Part 1 by Chris Wallis — source 2

  2. About OpenVDB by Peter Cucka — source 2

  3. Accelerating OpenVDB on GPUs with NanoVDB by Wil Braithwaite and Ken Museth — source 2 3

Open in stacked reader →