DirectX Raytracing Shader Table Explained
This article provides an overview of the shader table in DirectX
Raytracing (DXR), explaining its fundamental purpose, structural layout,
and the exact indexing calculation used to map geometric instances to
shader identifiers. You will learn how shader records are organized in
GPU memory and how parameters from both acceleration structures and the
TraceRay function dynamically determine which hit group
executes for any given ray-primitive intersection.
What Is the Shader Table?
In DirectX Raytracing, the shader table is a contiguous block of GPU
memory allocated by the application that defines the relationship
between ray types, geometry instances, and shader programs. Unlike
traditional rasterization pipelines that bind shaders via explicit
command list calls (e.g., SetPipelineState), DXR
dynamically selects and executes shaders directly on the GPU based on
ray traversal results.
The shader table is divided into distinct sections corresponding to different stages of the ray tracing pipeline:
- Ray Generation Table: Contains records for the entry-point shaders that initiate rays.
- Miss Shader Table: Contains records invoked when a ray fails to intersect any geometry.
- Hit Group Table: Contains records for geometry intersections, which can include Any Hit, Closest Hit, and Intersection shaders.
- Callable Shader Table: Contains records for execution routines invoked directly by other shaders.
Anatomy of a Shader Record
The fundamental unit within a shader table is the Shader
Record. Each record is a sequence of bytes aligned to
D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT (32 bytes)
that contains two elements:
- Shader Identifier (32 bytes): An opaque handle
retrieved from the State Object via
ID3D12StateObjectProperties::GetShaderIdentifier(). This uniquely identifies the compiled shader or hit group. - Local Root Arguments: Application-defined data, constants, descriptors, or resource views (CBVs, SRVs, UAVs) bound locally to that specific record via a local root signature.
How Geometries Map to Shader Identifiers
The mapping process occurs during the hit testing phase of a ray
traversal. When a ray intersects a primitive, DXR must resolve which hit
group shader record in the shader table to execute. This resolution
relies on a dedicated indexing formula that combines variables defined
in the acceleration structures with variables passed to the
TraceRay() call in HLSL.
The Indexing Formula
To locate the memory address of the appropriate hit group record, the system calculates the zero-based record index using the following formula:
\[\text{HitGroupIndex} = \text{RayContributionToHitGroupIndex} + (\text{MultiplierForGeometryContributionToHitGroupIndex} \times \text{GeometryIndex}) + \text{InstanceContributionToHitGroupIndex}\]
Once the index is determined, the memory address is evaluated as:
\[\text{RecordAddress} = \text{HitGroupTableBaseAddress} + (\text{HitGroupIndex} \times \text{HitGroupTableStride})\]
Components of the Formula
RayContributionToHitGroupIndex(fromTraceRay): An offset defined by the shader author callingTraceRay(). It specifies the ray type, such as0for primary radiance rays or1for shadow rays.MultiplierForGeometryContributionToHitGroupIndex(fromTraceRay): Specifies the stride across ray types. It typically equals the total number of ray types present in the application (for example,2if the system supports radiance and shadow rays).GeometryIndex(from the Bottom-Level Acceleration Structure): An implicit, zero-based index generated by DXR based on the order in which geometries were registered within theD3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTSdescriptor for that specific mesh.InstanceContributionToHitGroupIndex(from the Top-Level Acceleration Structure): An offset assigned per instance inside theD3D12_RAYTRACING_INSTANCE_DESCstructure. It allows different instances of the same mesh to point to completely different sets of hit records.
Practical Example of Hit Group Mapping
Consider a scene with two ray types: Radiance (index 0) and Shadow
(index 1). In this setup, the
MultiplierForGeometryContributionToHitGroupIndex is set to
2.
If a mesh in a Bottom-Level Acceleration Structure (BLAS) contains two geometries (e.g., an opaque body as Geometry 0, and a transparent windshield as Geometry 1):
- Instance 0 (e.g., a car):
- Set
InstanceContributionToHitGroupIndex = 0. - Geometry 0 (Body):
- Radiance Ray:
0 + (2 * 0) + 0 = Record 0 - Shadow Ray:
1 + (2 * 0) + 0 = Record 1
- Radiance Ray:
- Geometry 1 (Windshield):
- Radiance Ray:
0 + (2 * 1) + 0 = Record 2 - Shadow Ray:
1 + (2 * 1) + 0 = Record 3
- Radiance Ray:
- Set
- Instance 1 (a second car with different materials):
- Set
InstanceContributionToHitGroupIndex = 4to shift past the first car's records. - Geometry 0 (Body):
- Radiance Ray:
0 + (2 * 0) + 4 = Record 4 - Shadow Ray:
1 + (2 * 0) + 4 = Record 5
- Radiance Ray:
- Geometry 1 (Windshield):
- Radiance Ray:
0 + (2 * 1) + 4 = Record 6 - Shadow Ray:
1 + (2 * 1) + 4 = Record 7
- Radiance Ray:
- Set
This mathematical decoupling allows the GPU to resolve shading operations and localized material resources for complex scenes without requiring dynamic control flow or manual pointer indirection in shader code.