3D Textures for Volumetric Density in DirectX

Volume textures, or 3D textures, represent volumetric density fields in DirectX by organizing data into a three-dimensional grid of voxels defined by width, height, and depth. This article explains how DirectX resources store scalar density values, how normalized three-dimensional coordinates are used to access this data in HLSL shaders, how hardware trilinear interpolation reconstructs continuous media, and how techniques like raymarching utilize these textures to render phenomena such as smoke, clouds, and medical scans.

The Architecture of 3D Textures in DirectX

In DirectX (Direct3D 11 and Direct3D 12), a 2D texture consists of an \((X, Y)\) grid of pixels, whereas a volume texture is an \((X, Y, Z)\) grid of volume elements (voxels). In Direct3D 11, this is represented by the ID3D11Texture3D interface, and in Direct3D 12, it is created as an ID3D12Resource with the dimension set to D3D12_RESOURCE_DIMENSION_TEXTURE3D.

Unlike a 2D texture array, which treats individual slices as isolated layers, a 3D texture treats the depth dimension as a continuous spatial axis. Memory is laid out to allow the GPU to efficiently cache and read data along all three spatial dimensions simultaneously.

Representing Density Fields with Formats

A volumetric density field maps physical space to a scalar value that indicates the presence, concentration, or opacity of a medium at any given point. To store this scalar data efficiently, volume textures typically use single-channel DXGI_FORMAT types:

  • DXGI_FORMAT_R8_UNORM: Stores normalized 8-bit values from 0.0 to 1.0. This is memory-efficient and suitable for static, low-frequency density fields like basic fog or stylized clouds.
  • DXGI_FORMAT_R16_FLOAT or DXGI_FORMAT_R32_FLOAT: Provides 16-bit or 32-bit floating-point precision. These are ideal for physical simulations, scientific visualizations, or high-dynamic-range densities where abrupt gradients or large scale variations occur.
  • Multi-channel formats (e.g., DXGI_FORMAT_R8G8B8A8_UNORM): Used when density is coupled with additional attributes, such as storing density in the Alpha channel and directional gradients (normals for lighting) or emission color in the RGB channels.

Sampling and Trilinear Filtering in HLSL

In High-Level Shading Language (HLSL), a 3D texture is declared using the Texture3D object and accessed with three-dimensional normalized texture coordinates \((u, v, w)\), each ranging from \(0.0\) to \(1.0\):

Texture3D<float> g_DensityVolume : register(t0);
SamplerState g_LinearSampler : register(s0);

float SampleDensity(float3 uvw)
{
    return g_DensityVolume.Sample(g_LinearSampler, uvw);
}

A crucial advantage of using native 3D textures in DirectX is hardware-accelerated trilinear filtering. When querying a coordinate that falls between discrete voxels, the GPU fetches the eight surrounding voxels (two sets of four across adjacent depth slices) and linearly interpolates the values across the X, Y, and Z axes in hardware. This creates a continuous scalar field from discrete samples without requiring manual interpolation in shader code.

Address modes configured via D3D12_SAMPLER_DESC (such as D3D12_TEXTURE_ADDRESS_MODE_CLAMP or D3D12_TEXTURE_ADDRESS_MODE_BORDER) also allow developers to control boundary conditions, ensuring density naturally drops to zero outside the texture bounds.

Volumetric Rendering: Raymarching

To visualize the density field, graphics pipelines typically use a technique called raymarching within a pixel or compute shader:

  1. Ray Setup: For a given view ray intersecting the bounding volume, entry and exit points are calculated.
  2. Sampling Loop: The ray steps forward in uniform intervals \(\Delta t\). At each step, world-space positions are transformed into \((u, v, w)\) coordinates to sample the density texture.
  3. Optical Depth and Extinction: The sampled density values are multiplied by step length and an extinction coefficient to determine how much light the medium absorbs or scatters, commonly evaluated using the Beer-Lambert law (\(T = e^{-\tau}\)).
  4. Accumulation: The shader accumulates opacity and in-scattered light along the ray until the ray exits the volume or the medium becomes fully opaque.

Dynamic Density Fields via Compute Shaders

3D textures are not restricted to static assets. DirectX supports writing directly to volume textures using Unordered Access Views (UAVs). Compute shaders can bind a RWTexture3D<float> to run 3D fluid simulations (such as Navier-Stokes solvers). The compute shader updates voxel densities and velocities frame-by-frame, and the resulting 3D texture is immediately bound as a Shader Resource View (SRV) for the rendering pass, avoiding costly CPU-GPU memory transfers.