Ray Tracing Non-Triangle Primitives in DirectX
DirectX Raytracing (DXR) provides dedicated hardware acceleration for testing ray intersections against triangle meshes, but rendering custom geometric shapes requires a different approach. The intersection shader enables developers to bypass the fixed-function triangle test and execute user-defined mathematical equations to detect ray hits against non-triangle primitives, such as spheres, cylinders, curves, or volumetric fields. By enclosing custom shapes within axis-aligned bounding boxes (AABBs) and handling intersection math directly via HLSL, developers can seamlessly integrate arbitrary procedural geometries into the standard DXR pipeline.
Procedural Geometry in the Acceleration Structure
To ray trace non-triangle primitives, the geometry must be declared
to the Bottom-Level Acceleration Structure (BLAS) as procedural
primitives rather than standard triangle meshes. Instead of vertex and
index buffers, developers supply the API with an array of bounding boxes
defined by
D3D12_RAYTRACING_GEOMETRY_TYPE_PROCEDURAL_PRIMITIVE_AABB.
During the acceleration structure build process, the system organizes these bounding volumes into the bounding volume hierarchy (BVH). The underlying hardware or driver traverses this BVH using standard ray-box intersection algorithms. The custom intersection shader is only invoked when a ray actually penetrates one of these bounding boxes.
Execution of the Intersection Shader
When a ray intersects a procedural primitive's AABB during traversal, the pipeline temporarily suspends hardware traversal and launches the assigned intersection shader.
The shader receives contextual information through system-value intrinsics, including:
ObjectRayOrigin()andObjectRayDirection(): The ray transformed into the local coordinate space of the procedural primitive.RayTMin()andRayTCurrent(): The valid parametric range along the ray where a hit can occur.PrimitiveIndex(): The identifier of the specific primitive within the geometry block being tested.
Inside the shader, the developer writes the analytic solution for the shape. For example, testing a sphere requires solving a quadratic equation to find the values of \(t\) where the ray intersects the sphere's surface. For hair or fur ribbons, developers evaluate spline intersections, while for implicit surfaces, ray marching or sphere tracing algorithms can be executed.
Reporting the Hit to the Pipeline
If the mathematical test yields a valid intersection within the range \([RayTMin(), RayTCurrent()]\), the shader informs the pipeline by calling the intrinsic function:
bool ReportHit(float tHit, uint HitKind, BuiltInTriangleIntersectionAttributes Attributes);
tHit: The parametric distance along the ray where the surface contact occurred.HitKind: A user-defined 8-bit identifier that can classify the type of hit (for instance, entering versus exiting a volume).Attributes: A user-defined structure passing surface data—such as surface normals, texture coordinates, or tangent vectors—calculated during the intersection test.
When ReportHit is called, the pipeline evaluates any
active Any-Hit shaders. If the hit is accepted,
RayTCurrent() shrinks to tHit, ensuring that
subsequent intersection tests only consider closer geometry. If the call
returns true, the intersection was accepted as the new
potential closest hit; if false, the hit was rejected
(e.g., by an Any-Hit shader).
Handoff to Downstream Shaders
Once all potential intersections along the ray have been tested and the traversal concludes, the pipeline invokes the Closest-Hit shader for the nearest confirmed hit.
The Closest-Hit shader receives the custom attribute structure passed
by the successful ReportHit call. This allows the shading
stage to calculate final lighting, reflection, and materials using
precise analytical data (such as exact mathematical normals) rather than
interpolated vertex normals, preserving visual fidelity for smooth
curved surfaces without requiring dense polygon meshes.