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_FLOATorDXGI_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:
- Ray Setup: For a given view ray intersecting the bounding volume, entry and exit points are calculated.
- 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.
- 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}\)).
- 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.