光线追踪

更新记录
  • 2023/5/12 创建本文

  • 2023/5/12 创建 VK_KHR_acceleration_structure 章节

  • 2023/5/12 创建 VK_KHR_ray_tracing_pipeline 章节

  • 2023/5/12 创建 VK_KHR_ray_query 章节

  • 2023/5/12 创建 VK_KHR_pipeline_library 章节

  • 2023/5/12 创建 VK_KHR_deferred_host_operations 章节

  • 2023/5/12 创建 光追最佳实践 章节

  • 2023/5/12 发布完成

文献源

Vulkan API 中有5个与光追相关的扩展

另外也发布了着色器对应 SPIR-VGLSL 所需要的扩展

备注

很多光追程序都需要分配大量连续内存,由于内存地址空间大小的限值,在32位系统上实现很考验实现能力。虽然可以在32位系统上实现,但是应用可能面临着时不时的内存问题,比如分配内存失败。 此外一些驱动实现可能根本不会在32位系统中显示支持。

VK_KHR_acceleration_structure

加速结构在光追中用于描述几何物体的(内部具体实现是驱动自定义的)。通过将几何体构建进加速结构,光追可基于此种已知的数据布局进行高效计算。 VK_KHR_acceleration_structure 扩展提供了构建和拷贝加速结构的功能,并且支持与内存进行序列化。

加速结构需要光追管线 VK_KHR_ray_tracing_pipeline 和光线查询 VK_KHR_ray_query 两个扩展

对于加速结构的创建:

备注

加速结构的 创建构建 是两个不同的东西,创建是创建加速结构实例,构建是构建加速结构内部数据和结构。

  • 使用 VkAccelerationStructureBuildGeometryInfoKHR 声明加速结构的类型、几何类型、数量和最大大小。此时真正的几何数据可以不指定。

  • 调用 vkGetAccelerationStructureBuildSizesKHR 获取构建加速结构时的需要的内存大小

  • 分配一个足够大的缓存用于存储加速结构( VkAccelerationStructureBuildSizesKHR::accelerationStructureSize )和暂存缓存( VkAccelerationStructureBuildSizesKHR::buildScratchSize

  • 调用 vkCreateAccelerationStructureKHR 创建一个加速结构(位于某一个缓存中特定位置)

  • 调用 vkCmdBuildAccelerationStructuresKHR 去构建加速结构,之前提到的 VkAccelerationStructureBuildGeometryInfoKHR 在此时会被作为参数,将会根据声明的加速结构实例、构建暂存缓存和真正的几何数据(顶点,索引和变换)进行构建

VK_KHR_ray_tracing_pipeline

VK_KHR_ray_tracing_pipeline 扩展用于光追管线。光追管线是区别于常见光栅化管线的独立渲染管线,光追管线使用一组专用着色器,与传统的顶点、几何、片元着色器独立。并且光追管线也提供独立的渲染指令( vkCmdTraceRaysKHRvkCmdTraceRaysIndirectKHR )。 这与传统的光栅化绘制类似( vkCmdDrawvkCmdDrawIndirect )。

为了追踪光线:

  • 使用 vkCmdBindPipeline 绑定光追管线于 VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR 上。

  • 调用 vkCmdTraceRaysKHRvkCmdTraceRaysIndirectKHR

与光追管线有关的着色器如下:

../_images/VK_KHR_ray_tracing_pipeline_shaders_stage.jpg
  • Ray generation shader 光线生成着色器,作为光追管线的起点。与计算着色器类似将会执行一组着色器调用( vkCmdTraceRaysKHRvkCmdTraceRaysIndirectKHR )。光线生成着色器将会生成光线并且通过在着色器中调用 traceRayEXT() 进行追踪。并且处理光线相交结果的集合。

  • Closest hit shaders 最近命中着色器将会在最近命中几何体时执行。应用支持任意数量的最近命中着色器。此着色器最常用于光照计算并继续追踪额外的光线。

  • Miss shaders 未命中着色器与最近命中着色器相反,当光线没有与任何几何体相交时未命中着色器会被调用。该着色器常用于采样环境纹理。

  • Intersection shaders 相交着色器允许自定义处理光线相交,并且内置的相交测试是基于三角形进行测试。

  • Any hit shaders 任意命中着色器,与最近命中着色器类似,任意命中着色器在检测到发生相交时调用,任意命中着色器不同的是只要相交发生在 [tmin, tmax] 之间而不是最近的一次命中。任意命中着色器用于过滤相交和透明度测试。

VK_KHR_ray_query

VK_KHR_ray_query 扩展支持在所有类型着色器中进行光线追踪,包括图形、计算和光追管线。

光线查询要求光线遍历代码必须位于着色器中。与光追管线不同的是,在光追管线中光线生成、求交测试和光线与几何体击中处理,都是在独立不同的着色器阶段。 所以光线查询允许在广泛的着色器阶段进行光线追踪,当然也有代价,这会限值 Vulkan 的驱动实现对于光追调度的优化。

该扩展不引入额外的 API 入口点,其仅仅使用 SPIR-VGLSL 的扩展( SPV_KHR_ray_queryGLSL_EXT_ray_query )。

VK_KHR_ray_query 所提供的功能与 VK_KHR_ray_tracing_pipeline 互补,并且两个扩展可以同时使用。

rayQueryEXT rq;

rayQueryInitializeEXT(rq, accStruct, gl_RayFlagsTerminateOnFirstHitEXT, cullMask, origin, tMin, direction, tMax);

// Traverse the acceleration structure and store information about the first intersection (if any)
rayQueryProceedEXT(rq);

if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionNoneEXT) {
    // Not in shadow
}

VK_KHR_pipeline_library

VK_KHR_pipeline_library 用于管线库,一个管线库是使用 VK_PIPELINE_CREATE_LIBRARY_BIT_KHR 创建的特殊管线,其并不能直接绑定和使用,而是用于代表一组着色器或着色器组和相关其他管线相关的状态。

VK_KHR_pipeline_library 并没有直接增加新 API 也没有定义如何创建管线库,而相关的功能是交于那些使用 VK_KHR_pipeline_library 提供功能的扩展。 当前仅仅提供了 VK_KHR_ray_tracing_pipeline 的例子。

当前仅仅提供了 VK_KHR_ray_tracing_pipeline 的例子

KhronosGroupVulkan-Samples 项目中目前已经不单单只有 VK_KHR_ray_tracing_pipeline 例子,还有 其他扩展示例

VK_KHR_pipeline_library 被定义成独立的扩展,为了是在未来其它扩展共用此扩展而不需要依赖于光追扩展。

对于创建光追管线库:

  • 当调用 vkCreateRayTracingPipelinesKHR 时指定 VkRayTracingPipelineCreateInfoKHR::flags 中有 VK_PIPELINE_CREATE_LIBRARY_BIT_KHR

对于将光追管线链接到一个完整管线中:

  • 设置 VkRayTracingPipelineCreateInfoKHR::pLibraryInfo 指向一个 VkPipelineLibraryCreateInfoKHR 实例指针

  • VkPipelineLibraryCreateInfoKHR::pLibraries 中设置的管线作为管线库中用于输入连接的管线,并且设置 VkPipelineLibraryCreateInfoKHR::libraryCount 设置适当值

VK_KHR_deferred_host_operations

VK_KHR_deferred_host_operations 提供了将繁重的 CPU 的工作通过多线程进行分摊的机制。 VK_KHR_deferred_host_operations 被设计成允许应用创建和管理线程。

VK_KHR_pipeline_library 类似, VK_KHR_deferred_host_operations 也是个独立的扩展,目的也是为了在未来其他扩展共用该扩展功能。

只有在标注了支持延迟操作时才可以进行延迟操作。当前支持的延迟操作为 vkCreateRayTracingPipelinesKHRvkBuildAccelerationStructuresKHRvkCopyAccelerationStructureKHRvkCopyMemoryToAccelerationStructureKHRvkCopyAccelerationStructureToMemoryKHR

为了操作时延迟的:

  • 通过 vkCreateDeferredOperationKHR 创建一个 VkDeferredOperationKHR 句柄

  • VkDeferredOperationKHR 作为参数调用需要的延迟操作

  • 通过返回的 VkResult 查看之前的操作结果:
    • VK_OPERATION_DEFERRED_KHR 表示延迟操作成功

    • VK_OPERATION_NOT_DEFERRED_KHR 表示操作立即成功完成了

    • 其他任意错误值表示有错误发生

将一个线程加入到一个延迟操作,并且消耗 CPU 时间去处理该操作:

  • 对于每个想要参与操作的线程调用 vkDeferredOperationJoinKHR

  • 通过 vkDeferredOperationJoinKHR 返回的 VkResult 查看操作结果:
    • VK_SUCCESS 表示操作完成

    • VK_THREAD_DONE_KHR 表示当前调用的线程已经没有要分配的工作了,但是其他的线程可能还在处理额外的工作。当前的线程不应该再通过 vkDeferredOperationJoinKHR 再次 join

    • VK_THREAD_IDLE_KHR 表示当前调用的线程暂时已经没有要分配的工作了,但是其他额外的工作可能会在不期到来。当前的线程应该执行其他有用的工作,并且调用 vkDeferredOperationJoinKHR 再次 join 以此达到高收益。

当一个延迟操作完成后(比如 vkDeferredOperationJoinKHR 返回了 VK_SUCCESS ),调用 vkGetDeferredOperationResultKHR 获取延迟操作的结果。

光追最佳实践

最小化并行访问光线查询对象的线程数量

光线查询对象对于线程的部分私有存储是非常昂贵的,对性能的消耗也是如此,所以用的越少越好。在绝大多数情况下应该使用一个光线查询对象就算追踪多条光线,当一个结束的 光线发射了其他光线的话亦可以使用同一个光线查询对象。只用当多个光线遍历需要并行执行时光线查询才可在同一着色器中。

最小化光线、击中属性和可调用数据大小

光追着色器各阶段可以通过光线负载结构在所有的遍历阶段、遍历控制着色器的击中属性结构、可调用着色器的可调用数据之间进行参数和结果的访问。

这三个结构体都会消耗驱动管理内存,具体消耗多少依赖于结构本身、并行的光线数量和其他因素,比如递归层级。

着色器需要保持这些结构尽量的小。

多用设备本地的内存

设备本地的内存

设备本地的内存,多称为 device-localdevice 端,其多指 GPU 设备中的内存。而 host 端多指 CPU 使用的内存条的内存,为系统内存。 其实也不绝对,有时内存条上的部分内存为 device 端和 host 端公有的内存,具体设备具体实现。

加速结构可以建立在 Vulkan 的任何内存堆上,使用内存在本地设备的加速结构进行光线追踪会得到更加优良的性能,并且应该是首选。 对于使用 host 端的内存(比如 GPU 可访问的系统内存)只有当设备本地数据的大小有限时才考虑使用,其光追性能大概率到不了设备本地内存的性能。