光线追踪

一组五个相互关联的扩展在 Vulkan API 中提供光线追踪支持。

其他 SPIR-V 和 GLSL 扩展也公开了着色器所需的必要可编程功能

许多光线追踪应用程序需要大型连续内存分配。由于地址空间的大小有限,这在 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)有些类似。

要跟踪光线

  • 使用 vkCmdBindPipelineVK_PIPELINE_BIND_POINT_RAY_TRACING_KHR 绑定光线追踪管线

  • 调用 vkCmdTraceRaysKHRvkCmdTraceRaysIndirectKHR

光线追踪管线引入了几个新的着色器域。这些如下所述

Ray Tracing Shaders
  • 光线生成着色器表示光线追踪的起点。光线追踪命令(vkCmdTraceRaysKHRvkCmdTraceRaysIndirectKHR)启动一个着色器调用网格,类似于计算着色器。光线生成着色器构造光线,并通过调用 traceRayEXT() 开始跟踪。此外,它还会处理命中组的结果。

  • 最近命中着色器在光线与最近的几何体相交时执行。应用程序可以支持任意数量的最近命中着色器。它们通常用于执行照明计算,并且可以递归地跟踪其他光线。

  • 当光线在遍历期间未与任何几何体相交时,将执行未命中着色器,而不是最近命中着色器。未命中着色器的常见用途是采样环境贴图。

  • 内置的相交测试是光线-三角形测试。相交着色器允许自定义相交处理。

  • 类似于最近命中着色器,任何命中着色器在报告相交后执行。不同之处在于,任何命中着色器都会针对由 [tmin, tmax] 定义的射线区间内的任何相交调用,而不是针对射线原点最近的相交调用。任何命中着色器用于过滤相交,因此通常用于实现 alpha 测试。

VK_KHR_ray_query

VK_KHR_ray_query 扩展提供了从所有着色器类型(包括图形、计算和光线追踪管线)追踪光线的支持。

光线查询要求光线遍历代码显式包含在着色器中。这与光线追踪管线不同,在光线追踪管线中,光线生成、相交测试和光线-几何体命中处理表示为单独的着色器阶段。因此,尽管光线查询允许从更广泛的着色器阶段追踪光线,但它也限制了 Vulkan 实现可能应用于光线调度和追踪的优化范围。

该扩展不引入额外的 API 入口点。它只是为相关的 SPIR-V 和 GLSL 扩展(SPV_KHR_ray_queryGLSL_EXT_ray_query)提供 API 支持。

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_pipelineVK_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 的设计目的是允许应用程序创建和管理线程,而不是在 Vulkan 驱动程序中引入线程池。

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 重新加入

    • VK_THREAD_IDLE_KHR 表示暂时没有工作要分配给调用线程,但将来可能会有其他工作可用。当前线程可能会在调用线程上执行一些其他有用的工作,并且稍后再次调用 vkDeferredOperationJoinKHR 重新加入可能会证明是有益的

在操作完成后(即 vkDeferredOperationJoinKHR 返回 VK_SUCCESS),调用 vkGetDeferredOperationResultKHR 以获取操作的结果。

光线追踪的同步

  • 对于着色器中的追踪或查询调用,请将 VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR 与加速结构的相应着色器阶段结合使用

  • 对于光线追踪管线中着色器绑定表的访问,请将 VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHRVK_ACCESS_SHADER_READ_BITVK_ACCESS_2_SHADER_BINDING_TABLE_READ_BIT_KHR 结合使用

  • 对于加速结构构建,请将 VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR 与与正在访问的资源对应的访问位结合使用

    • 目标 AS 使用 VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR

    • 源 AS(例如,用于更新)使用 VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR

    • 暂存缓冲区需要 VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHRVK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR

    • 顶点/索引/实例/变换缓冲区使用 VK_ACCESS_SHADER_READ_BIT

  • 对于加速结构复制命令,请使用 VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_COPY_BIT_KHRVK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,同样,访问标志取决于源

    • 目标 AS 使用 VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR

    • 源 AS 使用 VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR

    • 目标缓冲区使用 VK_ACCESS_TRANSFER_WRITE_BIT

    • 源缓冲区使用 VK_ACCESS_TRANSFER_READ_BIT

  • 对于间接追踪调用,间接缓冲区是 VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT,具有 VK_ACCESS_INDIRECT_COMMAND_READ_BIT

  • 对于间接加速结构构建,间接缓冲区是 VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,具有 VK_ACCESS_INDIRECT_COMMAND_READ_BIT

  • 对于微映射构建,请将 VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT 与与正在访问的资源对应的访问位结合使用

    • 目标微映射使用 VK_ACCESS_2_MICROMAP_WRITE_BIT_EXT

    • 暂存缓冲区需要 VK_ACCESS_2_MICROMAP_WRITE_BIT_EXTVK_ACCESS_2_MICROMAP_READ_BIT_EXT

    • 输入缓冲区使用 VK_ACCESS_SHADER_READ_BIT

  • 对于微映射复制命令,请使用 VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT,同样,访问标志取决于源

    • 目标微映射使用 VK_ACCESS_2_MICROMAP_WRITE_BIT_EXT

    • 源微映射使用 VK_ACCESS_2_MICROMAP_READ_BIT_EXT

    • 目标缓冲区使用 VK_ACCESS_TRANSFER_WRITE_BIT

    • 源缓冲区使用 VK_ACCESS_TRANSFER_READ_BIT

与其他复制操作不同,VK_PIPELINE_STAGE_TRANSFER_BIT 不适用于加速结构复制。

使用 VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_COPY_BIT_KHR/ VK_ACCESS_2_SHADER_BINDING_TABLE_READ_BIT_KHR 需要 VK_KHR_ray_tracing_maintenance1

使用 VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT/ VK_ACCESS_2_MICROMAP_WRITE_BIT_EXT/VK_ACCESS_2_MICROMAP_READ_BIT_EXT 需要 VK_EXT_opacity_micromap

光线追踪最佳实践

最大限度地减少并发活动的光线查询对象的数量

光线查询对象在线程私有存储方面可能很昂贵,因此为了提高性能,最好尽可能少使用。在大多数情况下,即使追踪多条光线,也应该可以使用单个光线查询对象,因为正在发出新光线的终止光线可以使用相同的对象。只有当多个遍历需要同时处于活动状态时,才需要在同一个着色器中使用多个光线查询,并且着色器的设计应最大限度地减少活动遍历的数量。

最大限度地减少光线有效载荷、命中属性和可调用数据的大小

光线追踪着色器阶段可以使用光线载荷结构在所有遍历阶段之间传递参数和结果,使用遍历控制着色器的命中属性结构,以及使用可调用着色器的可调用数据结构。

所有这三种结构都会消耗驱动程序管理的内存,其总数量可能会根据结构本身的大小、并发活动的射线数量以及诸如递归级别等其他因素进行扩展。

着色器应尽量保持这些结构的大小较小。

首选设备本地内存

虽然加速结构可以在任何 Vulkan 内存堆上构建,但在位于设备本地内存中的加速结构上进行光线追踪应该能提供最佳性能,并且应该优先考虑。在应用程序受到可用设备本地内存数量限制的情况下,可能需要使用主机本地内存(即 GPU 可访问的系统内存),但这不太可能提供与设备本地内存上的光线追踪相同的性能。