GLSL Ray Queries: Validating Procedural Hits

GLSL ray queries enable shaders to traverse acceleration structures inline, halting at candidate intersections to allow user code to accept, reject, or compute hit results. When traversing procedural geometry defined by Axis-Aligned Bounding Boxes (AABBs), ray queries pause to let the developer determine if a mathematical primitive inside the bounding volume was actually struck. This article explains the procedural candidate hit validation lifecycle in GL_EXT_ray_query, detailing the operational boundary between custom intersection logic, parametric hit generation, and the confirmation routines that commit intersections to the ray traversal state.

Ray Query Traversal and Candidate Discovery

During an inline ray query traversal loop driven by rayQueryProceedEXT(), the traversal stops whenever the hardware traversal unit encounters an intersection that cannot be automatically resolved. For procedural geometry, the acceleration structure stores bounding boxes rather than explicit polygons.

When a ray intersects an AABB, rayQueryProceedEXT() returns true, and calling rayQueryGetIntersectionTypeEXT(rayQuery, false) yields gl_RayQueryCandidateIntersectionAABBEXT. This flags that a procedural primitive's bounding volume has been entered, temporarily suspending traversal so that the shader can execute analytical intersection math.

Procedural Generation vs. Triangle Confirmation

A critical architectural distinction in GL_EXT_ray_query is how triangle and procedural candidates are confirmed:

  • Triangle Candidates (rayQueryConfirmIntersectionEXT): Built-in fixed-function intersection units automatically calculate triangle barycentrics and the intersection distance \(t\). The shader only needs to decide whether to accept or ignore the hit (for example, evaluating alpha masking) using rayQueryConfirmIntersectionEXT(rayQuery).
  • Procedural Candidates (rayQueryGenerateIntersectionEXT): Because an AABB only represents a bounding volume, no hit point or parametric distance \(t\) exists until the shader calculates it. Therefore, procedural hits are not confirmed via the parameterless rayQueryConfirmIntersectionEXT(). Instead, they are validated and submitted using rayQueryGenerateIntersectionEXT(rayQuery, float tHit).

Attempting to execute rayQueryConfirmIntersectionEXT() on a candidate of type gl_RayQueryCandidateIntersectionAABBEXT is invalid according to the SPIR-V and GLSL specifications; the procedural validation path strictly requires generating an explicit distance \(t\).

The Procedural Hit Validation Workflow

Validating a procedural candidate within a GLSL ray query involves a sequential execution flow:

  1. Candidate Identification: After rayQueryProceedEXT() returns true, verify that the candidate is procedural:

    if (rayQueryGetIntersectionTypeEXT(rayQuery, false) == gl_RayQueryCandidateIntersectionAABBEXT) {
        // Proceed with custom procedural intersection
    }
  2. Ray Space Transformation: Retrieve the object-space ray parameters for the candidate primitive using built-in query accessors such as rayQueryGetIntersectionObjectRayOriginEXT(rayQuery, false) and rayQueryGetIntersectionObjectRayDirectionEXT(rayQuery, false).

  3. Analytical Intersection: Execute user-defined intersection equations (such as ray-sphere, ray-cylinder, or signed distance field raymarching) within the local bounding space. This test yields a candidate hit distance \(t\).

  4. Range Testing and Submission: Verify that the computed \(t\) satisfies the current ray bounds (\(t_{\text{min}} \le t \le t_{\text{max}}\)). If the hit is valid, invoke:

    rayQueryGenerateIntersectionEXT(rayQuery, tHit);

State Commitment and Traversal Shortening

Executing rayQueryGenerateIntersectionEXT() validates the procedural hit by communicating the calculated distance back to the traversal engine. If \(tHit\) is closer than the current committed intersection distance and lies within the active \([t_{\text{min}}, t_{\text{max}}]\) interval, the query replaces the current candidate state with this hit.

Upon the next invocation of rayQueryProceedEXT(), the traversal engine commits this intersection, effectively clamping \(t_{\text{max}}\) to the newly validated distance. This dynamic ray shortening prunes subsequent, more distant bounding volumes from triggering false procedural candidates throughout the remainder of the traversal.