vulkan 光线追踪
1.准备部分
需要添加的device extension:VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME以及 VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME在创建device的过程中,需要将其加到 VkDeviceCreateInfo 结构体的 ppEnabledExtensionNames 中。
1 | vector<const char*> usedExtensions; |
之后,我们可以通过VkPhysicalDeviceRayTracingPipelinePropertiesKHR 结构体取得vulkan ray tracing一些属性。具体方法为,将 VkPhysicalDeviceRayTracingPipelinePropertiesKHR 绑定到 VkPhysicalDeviceProperties2 的pNext上通过 vkGetPhysicalDeviceProperties2 取得。
1 | VkPhysicalDevice physicalDevice;//已经创建好的physical device |
2.加速结构acceleration structure
在用户视角下,加速结构分为两层:全局只有一个的TLAS和装载着具体数据的BLAS:
BLAS包含了模型的顶点数据,它可以由多个vertex buffer构建,每个vertex buffer可以携带一个transform matrix信息来表示其在这个BLAS的位置偏移。
每个TLAS包含了多个instance数据,每个instance含有一个transform matrix并引用一个BLAS数据

2.1 Bottom Level Acceleration Structure
2.1.1 Build Bottom Level Acceleration Structure
Blas由 VkAccelerationStructureKHR 和 VkBuffer 构成,通过调用vkCmdBuildAccelerationStructuresKHR创建。在构建过程中需要初始化一下几个结构体:
VkAccelerationStructureGeometryKHR 和VkAccelerationStructureBuildRangeInfoKHR 描述了构建BLAS需要的几何数据的格式,地址,类型等信息。(一个Model下的submesh 对应一个Geometry和BuildRangeInfo)
VkAccelerationStructureGeometryKHR 声明如下
1 | typedef struct VkAccelerationStructureGeometryKHR { |
对于最常见的三角网格,其类型为 VK_GEOMETRY_TYPE_TRIANGLES_KHR ,与其对应的geometry为 VkAccelerationStructureGeometryTrianglesDataKHR
1 | typedef struct VkAccelerationStructureGeometryTrianglesDataKHR { |
需要注意的是这里并不需要像Vertex Attributes一样把每个vertex的所有信息描述一遍,而只需要填写vertex的position数据对应的位置即可。这里的vertexData和indexData关联的buffer均需要在创建的时候设置flag VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR. 否则vulkan debug layer会报错:

VkAccelerationStructureBuildRangeInfoKHR 的作用类似index buffer,其定义如下:
1 | typedef struct VkAccelerationStructureBuildRangeInfoKHR { |
其transformOffset和 VkAccelerationStructureGeometryTrianglesDataKHR 中的 transformData 一起,描述了一个triangle的transform信息。官方文档对其有如下解释:
- If VkAccelerationStructureGeometryTrianglesDataKHR::
transformDatais notNULL, a single VkTransformMatrixKHR structure is consusmed from VkAccelerationStructureGeometryTrianglesDataKHR::transformData, at an offset oftransformOffset. This matrix describes a transformation from the space in which the vertices for all triangles in this geometry are described to the space in which the acceleration structure is defined.
填写好模型的描述信息后,我们可以开始着手构建acceleration structure。
对于每个BlAS,我们在构建时需要一个对应的 VkAccelerationStructureBuildGeometryInfoKHR,VkAccelerationStructureBuildSizesInfoKHR 以及对应的一系列VkAccelerationStructureBuildRangeInfoKHR (每个submesh一个)。
VkAccelerationStructureBuildGeometryInfoKHR 定义如下:
1 | typedef struct VkAccelerationStructureBuildGeometryInfoKHR { |
VkAccelerationStructureBuildSizesInfoKHR 定义如下:
1 | // Provided by VK_KHR_acceleration_structure |
scratch buffer是什么官方文档没有明确的解释,我个人认为它是用来存放build过程中产生数据的临时buffer需要在build之前构建并在build之后释放。在创建scratch buffer时至少需要两个flag:VK_BUFFER_USAGE_STORAGE_BUFFER_BIT (https://vulkan.lunarg.com/doc/view/1.3.216.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdBuildAccelerationStructuresKHR-pInfos-03674)和`VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` (为了获取device address)。
VkAccelerationStructureBuildSizesInfoKHR 的信息可以通过vkGetAccelerationStructureBuildSizesKHR 获取。
总的来说,构建Blas的流程如下(下面的代码中(A,B)代表A,B组成的元组):
1 | vector<(vector<VkAccelerationStructureGeometryKHR>,vector<VkAccelerationStructureBuildRangeInfoKHR>)> geometies; |
2.1.2 Build Compacted Bottom Level Acceleration Structure
除上述方法之外我们还可以构建一种数据组织的更紧凑的BLAS。(TODO)
2.2 Build Top Level Acceleration Structure
与Blas相同一个Tlas由VkAccelerationStructureKHR 和 VkBuffer 构成,通过调用vkCmdBuildAccelerationStructuresKHR构建。
构建Tlas同样需要通过VkAccelerationStructureBuildGeometryInfoKHR 描述几何信息。与Blas不同的是,一个Tlas只有一个geometry (geometryCount 为1)— VkAccelerationStructureGeometryInstancesDataKHR 。其定义如下:
1 | typedef struct VkAccelerationStructureGeometryInstancesDataKHR { |
其中data为指向该Tlas下所有instance的buffer的指针。每个instance由一个VkAccelerationStructureInstanceKHR 描述:
1 | typedef struct VkAccelerationStructureInstanceKHR { |
同时,对于Tlas一个instance为一个primitive,因此VkAccelerationStructureBuildRangeInfoKHR 中primitiveCount 为 instance数量,其余值为0。
其余部分,Tlas和Blas几乎相同,可以参照构建Blas的代码实现。
3.Ray Tracing Pipeline
一个ray tracing pipeline 如下图所示:

总的来说,rt pipeline一共有5类shader
- ray generation shader:对render target上每个pixel调用。整个光追流程的入口,通过该shader生成光线,向render target写入颜色。
- intersection shader: 主要用来与用户自定义的非三角网格几何体做相交测试(如隐式描述的几何体表面)
- any hit shader:一个ray的intersection可能有很多结果,由这个shader从这些结果中挑选出一个返回给cloest hit shader。对于内置的any hit shader,会返回离ray出发点最近的相交点。
- Miss shader:当ray没有击中场景时执行
- Closest Hit Shader:当ray击中场景时执行
3.1 Create Ray Tracing Pipeline and Shader Binding Table
在ray tracing中shader不再是像光栅化管线一样一个阶段一个shader的执行,而是所有shader放在一起形成一个shader binding table(SBT),再在每次traceRayExt中根据相交测试的结果从SBT中选择相应的shader执行。
在创建SBT之前,需要创建ray tracing pipeline,VkRayTracingPipelineCreateInfoKHR 定义如下:
1 | typedef struct VkRayTracingPipelineCreateInfoKHR { |
其中,除了sType没带注释的成员是选填的,我们也会重关注这些部分。
与graphics pipeline相同,我们需要为ray trace pipeline中每一个shader创建一个VkPipelineShaderStageCreateInfo。同时,我们还需要为这些shader创建Shader Group,VkRayTracingShaderGroupCreateInfoKHR 的定义如下
1 | typedef struct VkRayTracingShaderGroupCreateInfoKHR { |
在相交测试过程中,intersection shader,any hit shader和closest hit shader三个shader是紧密关联在一起的,它们会打包成一个shader group并形成一个handle(如对一个不透明球光线追踪,需要一个intersection shader和closest hit shader,它们可以打包成一个shader group并在shader代码中用一个handle引用)。而其它类型的shader(如ray generation和miss)则都是general shader,它们一个shader对应一个shader group。
在填写好VkRayTracingPipelineCreateInfoKHR后我们可以通过vkCreateRayTracingPipelinesKHR 创建ray tracing pipeline。
1 | vkCreateRayTracingPipelinesKHR(device,{},{},1,&rtCreateInfo,nullptr,&rtPipeline); |
在创建好shader stage,shader group以及ray tracing pipeline后,我们可以开始创建shader binding table。shader binding table就是一个装了若干shader group handle的buffer,在shader 代码中通过index来查找。vulkan对SBT的aligment有要求,这些信息可以在VkPhysicalDeviceRayTracingPipelinePropertiesKHR 中获取。一个shader binding table的内存分布如下图所示:

在SBT创建的过程中我们需要VkPhysicalDeviceRayTracingPipelinePropertiesKHR 中的三种信息:
shaderGroupHandleSize,每个shader group handle的大小。shaderGroupHandleAlignment在SBT中每个shader group handle的stride都需要一个alignment因此每个shader group handle的stride大小为
1 | //prop为VkPhysicalDeviceRayTracingPipelinePropertiesKHR |
3.shaderGroupBaseAlignment 每个SBT会根据不同的shader group类型(ray gen,hit,miss,callable)分为各个区域(上图中不同颜色标注的区域),而每个区域之间需要有alignment。因此,每个区域之间的stride为:
1 | //prop为VkPhysicalDeviceRayTracingPipelinePropertiesKHR |
总的来说,创建sbt的伪代码如下
1 | //sbt内存布局 |
3.2 Ray Trace Command
与光栅化管线不同,光追并不需要render pass。可以在vkAcquireNextImageKHR 之后直接开始渲染。
1 | vkAcquireNextImageKHR(...);//acquire下一帧 |
在rayTrace中我们需要通过调用vkCmdTraceRaysKHR进行光追。vkCmdTraceRaysKHR 需要sbt各个region的deviceaddress。我们可以通过3.1中sbt的内存布局计算得到:
1 | VkStridedDeviceAddressRegionKHR hitRegion,missRegion,rayGenRegion,callableRegion; |
4.Ray Tracing GLSL
首先在最开始需要开启glsl的光追模块
1 |
在ray tracing 中通过调用traceRayExt 生成光线,而光线击中场景后又会去调用相应的shader。这样两个shader之间形成了类似调用和被调用的关系。调用者和被调用者之间通过pay load交换数据。调用者可以声明多个rayPayloadEXT (一个location一个pay load),并在traceRayExt 中让一个rayPayloadExt可见,在被调用者中声明rayPayloadInEXT 来接收这个数据。
1 | //caller |
traceRayExt 的参数如下(具体可以看https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_tracing.txt)(之后的讨论围绕$ray=dir * t + origin$ 展开)
tlas : accelerationStructureEXT需要trace的场景的tlasflagstrace过程中的flag如gl_RayFlagsSkipClosestHitShaderEXT(跳过closest hit shader),gl_RayFlagsTerminateOnFirstHitEXT(在第一次击中就结束)等。mask : uint8光线的mask和tlas中的mask(见2.2)对应。当光线的mask能覆盖instance的mask时光线才能击中。sbtRecordOffset : uinthit shader的offsetsbtRecordStride : uinthit shader的stride。和offset一起决定hit的时候选用哪个hit shader。这个过程相当复杂具体可以看https://www.willusher.io/graphics/2019/11/20/the-sbt-three-ways Hit Group Shader Record Index Calculation 部分origin : vec3ray的出发点minRange : floatray的t的最小值dir : vec3ray的方向maxRange : floatray的t的最大值payload : const uintpayload 的 location
除此以外,glsl提供了一些有用的内置值(具体可以看https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_tracing.txt):
gl_LaunchIDEXT : uint2当前该shader的pixel idgl_LaunchSizeEXT : uint2vkCmdTraceRaysKHR命令中传入的width和heightgl_InstanceCustomIndexEXT : uint在closest hit shader中,被击中的instance的indexgl_PrimitiveID : uint在closest hit shader中,instance中被击中的三角形的id(对三角形网格来说)gl_ObjectToWorldEXT : Mat4x4在closest hit shader中, instance从object空间到world空间的变换矩阵gl_WorldToObjectEXT : Mat4x4在closest hit shader中,instance从world空间到object空间的变换据矩阵gl_WorldRayOriginEXT : vec3在closest hit shader中,击中该点的ray的世界坐标gl_WorldRayDirectionEXT : vec3在closest hit shader中,击中该点的ray的世界方向gl_HitTEXT : float在closest hit shader中,击中该点的ray的t值hitAttributeEXT修饰符,修饰存储了ray和instance相交点的信息。被修饰的变量只能在instance shader中写入,在closest hit shader和any hit shader中读取。在instance shader缺省,与三角形网格求交的过程中,会有一个hitAttributeEXT vec2 uv传入closest hit和any hit shader。通过它可以获取当前相交点的uv值。(https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_ray_tracing.asciidoc 5339部分)例如:1
2
3
4
5
6
7
8
9
10
11
12
13//a closest hit shader
...
hitAttributeEXT vec2 localUv;
...
void main(){
vec2 uv0,uv1,uv2;
vec2 uv = uv0 * (1.0 - localUv.x -localUv.y) + uv1 * localUv.x + uv2 * localUv.y;
...
}






