rayQueryGetIntersectionBarycentricsEXT in GLSL Ray Queries

This article explains how the rayQueryGetIntersectionBarycentricsEXT intrinsic retrieves triangle barycentric coordinates within inline GLSL ray queries using the GL_EXT_ray_query extension. It covers the function's signature, how the returned two-dimensional floating-point vector maps to a 3D triangle, the distinction between candidate and committed intersections, and how to use these coordinates to interpolate per-vertex attributes accurately.

Function Signature and Parameters

The rayQueryGetIntersectionBarycentricsEXT function is provided by the GL_EXT_ray_query SPIR-V extension for Vulkan. It extracts the hit location on a triangle primitive directly from a ray query object. Its signature is:

vec2 rayQueryGetIntersectionBarycentricsEXT(rayQueryEXT rayQuery, bool committed);

The function takes two arguments:

  1. rayQuery: The initialized rayQueryEXT object handling the traversal.
  2. committed: A boolean flag determining which intersection state to read. Passing false inspects the current candidate intersection while traversing. Passing true inspects the final committed intersection after traversal concludes or when accepting a hit.

The Returned Barycentric Format

The function returns a vec2 rather than a vec3. Ray tracing hardware and low-level APIs (such as Vulkan and DirectX Raytracing) follow standard normalized barycentric conventions for triangles:

  • The returned x component corresponds to the weight of vertex 1 (\(u\)).
  • The returned y component corresponds to the weight of vertex 2 (\(v\)).
  • The weight of vertex 0 (\(w\)) is implicit, calculated as \(1.0 - u - v\).

The sum of all three barycentric weights always equals 1.0:

\[w = 1.0 - uv.x - uv.y\]

Retrieving and Interpolating Attributes

To interpolate vertex attributes such as texture coordinates, normals, or colors, you must fetch the corresponding vertex data from storage buffers using the primitive index and multiply them by their respective barycentric weights.

// Ensure the hit is a triangle
if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) {
    // 1. Retrieve the two barycentric coordinates
    vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, true);
    
    // 2. Compute the third coordinate
    float w = 1.0 - bary.x - bary.y;
    vec3 weights = vec3(w, bary.x, bary.y);

    // 3. Fetch vertex attributes (e.g., UV coordinates from a buffer)
    int primID = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true);
    vec2 uv0 = vertices[indices[primID * 3 + 0]].uv;
    vec2 uv1 = vertices[indices[primID * 3 + 1]].uv;
    vec2 uv2 = vertices[indices[primID * 3 + 2]].uv;

    // 4. Interpolate across the triangle surface
    vec2 hitUV = uv0 * weights.x + uv1 * weights.y + uv2 * weights.z;
}

Candidate vs. Committed Intersections

The behavior of rayQueryGetIntersectionBarycentricsEXT depends on the traversal stage:

  • Candidate Intersections (committed = false): Used inside the while(rayQueryProceedEXT(rayQuery)) loop when handling non-opaque triangles. If custom alpha testing or any-hit logic is required, you can query candidate barycentrics to sample alpha textures before deciding whether to call rayQueryConfirmIntersectionEXT(rayQuery).
  • Committed Intersections (committed = true): Used after rayQueryProceedEXT returns false (meaning traversal is complete) or immediately after an intersection is confirmed. This yields the barycentrics of the closest confirmed hit along the ray.

Calling this function on an intersection type that is not a triangle (such as an AABB procedural primitive or a miss) produces undefined results. Always verify the intersection type with rayQueryGetIntersectionTypeEXT before reading barycentric data.