Introducing Vulkan Ray Tracing Position Fetch Extension
On April 27, 2023 the Vulkan® Ray Tracing TSG released the VK_KHR_ray_tracing_position_fetch extension, which exposes the ability to fetch vertex positions from an acceleration structure hit when tracing rays. The SPIR-V SPV_KHR_ray_tracing_position_fetch and GLSL GL_EXT_ray_tracing_position_fetch extensions have also been released to provide SPIR-V and GLSL support for this functionality.
The position of scene geometry is provided to ray tracing acceleration structures at build time and they include a derived form of the positions to enable efficient ray tracing and queries. Applications frequently require the position or a derived attribute of a triangle on a hit. For example, the geometric normal of the hit can be used as a biased ray origin for shadow rays in path tracers to prevent self intersection. The Ray Tracing Position Fetch extension enables direct retrieval of position and attribute information to avoid duplication of geometry data storage.
To use this extension, the application must set the VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR flag on the acceleration structure at build time. This flag is needed to ensure that implementations retain vertex position data at sufficient precision.
When used with ray tracing pipelines, this extension adds a new builtin variable, HitTriangleVertexPositionsKHR (gl_HitTriangleVertexPositionsEXT in GLSL), which is available in the any-hit and closest-hit shaders. When used with ray queries, this extension adds a new instruction, OpRayQueryGetIntersectionTriangleVertexPositionsKHR (rayQueryGetIntersectionTriangleVertexPositionsEXT in GLSL), which can be used with either candidate or committed triangle intersections. Both the builtin and the instruction return an array of the three, three-component vertex positions in object space of the triangle which was hit, which are transformed by the geometry transforms specified in the acceleration structures, but not the instance transform.
Below is a shader snippet showing the manual indexing and vertex fetch that is needed when the Ray Tracing Position Fetch extension is NOT supported. In addition to the extra code, the application must retain the vertex and index buffers and make them accessible to the hit shaders.
C/C++ /** Without ray tracing position fetch */ uint triIndex0 = indexBuffer[firstIndex + gl_PrimitiveID*3 + 0]; uint triIndex1 = indexBuffer[firstIndex + gl_PrimitiveID*3 + 1]; uint triIndex2 = indexBuffer[firstIndex + gl_PrimitiveID*3 + 2]; vec3 vertPos0 = vertexBuffer[triIndex0]; vec3 vertPos1 = vertexBuffer[triIndex1]; vec3 vertPos2 = vertexBuffer[triIndex2]; payload.geoNormal = cross(vertPos1 - vertPos0, vertPos2 - vertPos0);
The following is the simplified shader code that can be used when the Ray Tracing Position Fetch extension IS supported. The application may also discard the vertex and index buffers after the acceleration structure build, if they are not needed elsewhere.
C/C++ /** With ray tracing position fetch */ vec3 vertPos0 = gl_HitTriangleVertexPositionsEXT; vec3 vertPos1 = gl_HitTriangleVertexPositionsEXT; vec3 vertPos2 = gl_HitTriangleVertexPositionsEXT; payload.geoNormal = cross(vertPos1 - vertPos0, vertPos2 - vertPos0);
Application Use Case Study
Ray Tracing Position Fetch is a great quality of life and performance improvement for the Lumen dynamic global illumination system in Unreal Engine. To achieve high performance, Lumen uses a "Surface Cache" to store material and lighting data (a similar idea to texture space shading in other engines). All that is needed to access the Surface Cache is a world space position and surface normal.
Lumen supports software or hardware ray tracing, which is selected based on platform hardware capability and user preference. Lumen’s software ray tracing uses signed distance fields, which naturally allow it to compute an approximate normal at the ray hit point. However, the face normal is not typically available in hardware ray tracing APIs.
When the full ray tracing pipeline is used, with full material shader evaluation and lighting at a hit point, it is natural to bind index and vertex buffers to the Shader Binding Table and reconstruct the normals in hit shaders. However, this mode is quite expensive and generally reserved only for high-end platforms.
As a faster alternative, Lumen can also use "inline ray tracing" (AKA ray queries) in compute shaders. For this mode it is currently necessary to build a "lightweight SBT" that contains index and vertex buffer bindings, just to compute triangle normals. On a hit, the Lumen ray tracing shader has to load the relevant buffer binding record from the "lightweight SBT", load the triangle indices from the index buffer (appropriately handling 32 or 16 bit index format) and then finally load the vertex positions and compute the normal.
Triangle normals could also theoretically be precomputed per BLAS after a build or refit, which could eliminate some of the GPU-side overhead at the cost of some added application complexity.
If triangle vertex positions are directly available through Ray Tracing Position Fetch, all the current CPU and GPU overhead associated with SBT maintenance, extra resource access, etc. completely disappears. It is still necessary to compute the face normal and transform it from object to world space, but there is no longer a chain of indirect memory loads, assuming that the vertex positions can be loaded/decoded directly from BLAS data.
This is a very welcome addition to the Vulkan API that will make the lives of many developers a little bit easier, assuming there is no performance regression compared to explicit vertex and index buffer access.
- Yuriy O’Donnell, Epic
The Ray Tracing Position Fetch extension is available now in NVIDIA’s Vulkan Beta drivers. Mesa will add support for the extension in the next release of the open source ANV drivers for Intel GPUs, and AMD will add support in their next driver release. Please follow the Github release tracker for the status of the rollout of ecosystem components and driver status. We look forward to seeing how you put this new functionality to use!