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::
transformData
is 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的场景的tlasflags
trace过程中的flag如gl_RayFlagsSkipClosestHitShaderEXT
(跳过closest hit shader),gl_RayFlagsTerminateOnFirstHitEXT
(在第一次击中就结束)等。mask : uint8
光线的mask和tlas中的mask(见2.2)对应。当光线的mask能覆盖instance的mask时光线才能击中。sbtRecordOffset : uint
hit shader的offsetsbtRecordStride : uint
hit 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 : vec3
ray的出发点minRange : float
ray的t的最小值dir : vec3
ray的方向maxRange : float
ray的t的最大值payload : const uint
payload 的 location
除此以外,glsl提供了一些有用的内置值(具体可以看https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_tracing.txt):
gl_LaunchIDEXT : uint2
当前该shader的pixel idgl_LaunchSizeEXT : uint2
vkCmdTraceRaysKHR命令中传入的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;
...
}