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:

  1. Shader Identifier (32 bytes): An opaque handle retrieved from the State Object via ID3D12StateObjectProperties::GetShaderIdentifier(). This uniquely identifies the compiled shader or hit group.
  2. 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 (from TraceRay): An offset defined by the shader author calling TraceRay(). It specifies the ray type, such as 0 for primary radiance rays or 1 for shadow rays.
  • MultiplierForGeometryContributionToHitGroupIndex (from TraceRay): Specifies the stride across ray types. It typically equals the total number of ray types present in the application (for example, 2 if 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 the D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS descriptor for that specific mesh.
  • InstanceContributionToHitGroupIndex (from the Top-Level Acceleration Structure): An offset assigned per instance inside the D3D12_RAYTRACING_INSTANCE_DESC structure. 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):

  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
    • Geometry 1 (Windshield):
      • Radiance Ray: 0 + (2 * 1) + 0 = Record 2
      • Shadow Ray: 1 + (2 * 1) + 0 = Record 3
  2. Instance 1 (a second car with different materials):
    • Set InstanceContributionToHitGroupIndex = 4 to 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
    • Geometry 1 (Windshield):
      • Radiance Ray: 0 + (2 * 1) + 4 = Record 6
      • Shadow Ray: 1 + (2 * 1) + 4 = Record 7

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.