GLSL Barycentric Coordinates with hitAttributeEXT
In Vulkan ray tracing shaders, hardware intersection pipelines report
ray-triangle hit locations using a two-component vector instead of a
three-component coordinate. This article explains how to reconstruct the
full three-dimensional barycentric coordinates from the GLSL
hitAttributeEXT vec2 variable within closest-hit and
any-hit shaders, along with how to use those coordinates to interpolate
custom per-vertex attributes across a triangle.
The Barycentric Coordinate Principle
Any point \(P\) inside a triangle formed by vertices \(V_0\), \(V_1\), and \(V_2\) can be expressed as a linear combination of its vertices:
\[P = w V_0 + u V_1 + v V_2\]
Because the weights must sum to 1 (\(u + v + w = 1.0\)), only two scalar values are required to define any point on the triangle's surface. Hardware intersection units take advantage of this constraint to minimize memory bandwidth and register usage by returning only a two-component vector.
Declaring hitAttributeEXT
In your closest-hit (.rchit) or any-hit
(.rahit) shader, declare the built-in attribute using the
hitAttributeEXT storage qualifier:
hitAttributeEXT vec2 attribs;When using built-in triangle intersection, the hardware automatically
populates attribs with the parametric coordinates \((u, v)\) of the hit point.
Reconstructing the Three Components
Under the standard Vulkan and SPIR-V ray tracing conventions:
attribs.xcorresponds to \(u\), the weight of Vertex 1 (\(V_1\)).attribs.ycorresponds to \(v\), the weight of Vertex 2 (\(V_2\)).- The remaining component \(w\), representing the weight of Vertex 0 (\(V_0\)), is calculated as \(1.0 - u - v\).
To reconstruct the full vec3 barycentric weights in
GLSL:
vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y);In this reconstructed vector:
barycentrics.xmaps directly to \(V_0\).barycentrics.ymaps directly to \(V_1\).barycentrics.zmaps directly to \(V_2\).
Interpolating Vertex Attributes
Once reconstructed, the weights can be used to interpolate any vertex attribute fetched from your vertex or index buffers.
// Fetch indices for the hit triangle
uint primitiveID = gl_PrimitiveID;
uvec3 indices = indicesBuffer.indices[primitiveID];
// Fetch vertex data
Vertex v0 = verticesBuffer.vertices[indices.x];
Vertex v1 = verticesBuffer.vertices[indices.y];
Vertex v2 = verticesBuffer.vertices[indices.z];
// Interpolate normals
vec3 interpolatedNormal = v0.normal * barycentrics.x +
v1.normal * barycentrics.y +
v2.normal * barycentrics.z;
// Interpolate UV texture coordinates
vec2 interpolatedUV = v0.uv * barycentrics.x +
v1.uv * barycentrics.y +
v2.uv * barycentrics.z;Normalize interpolated normal vectors afterward to correct for non-linear scale drift across the primitive.