英伟达详述面向Vulkan的实时光线追踪技术
文章相关引用及参考:nvidia
本文来自英伟达开发技术工程师Nuno Subtil
(映维网 2018年11月12日)英伟达新推出的图灵显卡首次在消费者GPU中实现了实时光线追踪。自此,社区开始讨论DirectX 12中的光线追踪。然而,一系列的开发者都希望能够通过Vulkan(Khronos Group支持的low-level API)实现这一点,从而带来更为开放的策略。Vulkan令开发者能够针对众多不同的平台,包括Windows和Linux,从而允许更广泛地分发3D加速的应用程序。英伟达的411.63驱动程序现在启用了一个实验性的Vulkan扩展,它通过Vulkan API公开(expose)了英伟达用于实时光线追踪的RTX技术。
这个名为VK_NVX_raytracing的扩展属于开发者预览版。扩展程序面向希望熟悉API概念并开始测试相关功能的开发者。正如“NVX”前缀所示,这个API尚未最终确定,并且有可能在最终版本发布之前进行一定的更改。
日前,英伟达开发技术工程师Nuno Subtil向我们详细介绍了面向Vulkan的实时光线追踪技术,以下是映维网的具体整理:
1. 光线追踪API的演变
在英伟达,GPU加速的光线追踪成为研究课题已有超过10年的时间。
GPU逐渐发展成为功能强大的光栅化机器。为架构添加可编程性可以创建基于光栅化的复杂算法。这种可编程性令GPU能够处理更复杂的计算问题,并最终促成了英伟达GPU计算平台CUDA的推出。英伟达的GPU加速光线追踪研究专注于通过CUDA编程模型公开(expose)光线追踪。
英伟达努力的首个可见结果之一是OptiX,一种用于加速光线投射应用的通用SDK。英伟达在2010年SIGGRAPH大会发布的论文细介绍了针对OptiX的API设计研究,并包含了今天用于实时光线追踪的关键创建模块。图1是论文的摘录。
图1:早期论文的摘录,其详细介绍了关于OptiX API的研究
英伟达围绕OptiX所做的努力,特别是为了令其变得越来越高效和可扩展而进行的研发工作,最终在实时光线追踪(RTX)达到了顶峰。RTX是算法,计算机图形和GPU架构10多年研究的产物,它使得实时光线追踪应用程序能够在英伟达的GPU上运行。RTX利用了英伟达所有的研究工作和硬件改进,是创建实时光线追踪API的基础。
2. 为Vulkan带来光线追踪
英伟达Vulkan光线追踪扩展(简称“VKRay”)背后的关键设计原则利用了他们之前关于光线追踪API的研究。VKRay建立在经过验证和现场测试的API概念之上,其足够灵活,可以支持各种各样的应用程序,并同时仍然提供了运行应用程序利用未来研究的抽象层。
Vulkan是一个跨硬件API,而英伟达已经确保VKRay实现跨硬件支持。整个API可以在现有的Vulkan计算功能之上实现。英伟达同时在全力确保这个扩展符合现有的Vulkan API概念。内存分配,资源处理和着色器语言/字节码的处理方式与核心Vulkan API规定的方式相同。
3. 光线追踪管道
通过光线追踪管道的数据流不同于传统的光栅管道。图2比较了两个管道。
一般认为灰色块不可编程。随着底层实现的成熟,这一切都随着时间的推移而不断发展和改进。白色块代表完全可编程的阶段。钻石形阶段是调度工作发生的地方。
图2:传统光栅化管道 vs 光线追踪管道
与光栅化不同,光线追踪所执行的工作“单位(光线)”数量取决于先前工作单位的结果。这意味着新的工作能够通过可编程阶段产生,并直接馈送回管道。
英伟达光线追踪API包含四个关键组件:
- 加速结构
- 用于光线追踪的新shader domain
- Shader Binding Table
- 光线追踪管道对象
4. 加速结构
传统的光栅化涉及独立处理每一个几何图元。相比之下,光线追踪针对所有场景基元测试每条光线,而这样做的成本十分昂贵。
大多数光线追踪器是实现某种形式的加速结构,从而快速拒绝基元,英伟达的API将其公开(expose)为“一等公民”。加速结构(acceleration structure)是一个对象,它为场景中的基元保存几何信息,以这样的方式进行预处理,从而能够流畅地拒绝潜在的光线-基元交叉点。这是可以进行光线追踪的API基元。
加速结构(acceleration structure,AS)作为不透明的,实现定义的数据结构公开(expose),不依赖于任何基础算法或剔除方法。它建模为两级结构:bottom-level AS节点包含几何数据,而top-level AS节点则包含对底层节点的引用列表,以及相关的变换和着色信息。图3概述了结构之间的关系。
图3:top-level AS和bottom-level AS的关系
可以使用VkBuffer对象中的现有几何创建加速结构。创建AS的过程分为两步:首先创建bottom-level节点,然后生成top-level节点。
所有创建/更新操作都发生在GPU上,如图4所示。在这个情形下,“创建”意味着从零开始创建新对象(如在初始设置场景时),而“更新”则意味着我们通过新数据来更新现有对象(如当角色在现有场景中移动时)。尽管创建和更新操作都旨在以非常高效的方式进行,但两者都存在一定的限制,尤其是更新。
图4:加速结构流程
在API Level,我们定义一个新的Vulkan对象类型:VkAccelerationStructureNVX。对象的每个实例都在top-level AS节点或bottom-level封装。我们同时定义了一个新的资源描述符类型VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NVX,主要用于将AS对象绑定至着色器。
对象创建和消除遵循一般的Vulkan范例:通过调用vkCreateAccelerationStructureNVX创建加速结构节点,这会返回不透明句柄。然后,这个句柄可与vkGetAccelerationStructureMemoryRequirementsNVX一起使用,以获取有关该加速结构需要的内存类型和内存量信息。接下来,可以使用vkAllocateMemory分配内存,并通过调用vkBindAccelerationStructureMemoryNVX将其绑定至该对象。
4.1 管理加速结构内存
加速结构创建/更新操作需要一定的临时“暂存(scratch memory)”。可以通过调用vkGetAccelerationStructureScratchMemoryRequirementsNVX来查询这一内存。
暂存(scratch memory)以常规VkBuffer对象的形式出现,根据实现返回的内存要求进行分配。这将作为创建/更新命令的参数进行传入。
AS对象所需的内存量取决于传入的特定几何数据。因此,vkGetAccelerationStructureMemoryRequirementsNVX返回的数据是特定对象所需内存量的上限。创建结构后,可以使用vkCmdWriteAccelerationStructurePropertiesNVX命令,让GPU将给定AS对象的压缩大小写入Vulkan查询对象,而这可以在CPU进行读取。然后,可以使用它来分配具有所需内存量的单独AS对象,并且可以使用vkCmdCopyAccelerationStructureNVX来将原始AS对象压缩为新的AS对象。
4.2 加速结构创建/更新执行
AS创建/更新命令在GPU上执行,可以提交给图形或计算队列。API允许实现并行化连续的创建/更新命令,从而最大化GPU利用率。
在创建/更新命令之间重用暂存缓冲区时应小心,因为它们的执行可能会重叠。为确保正确,我们需要barrier同时,使用新的存储器访问flag位VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_NVX和VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_NVX:应该在暂存缓冲区上的缓冲存储器barrier中使用它们,然后再将相同的缓冲区用于另一个操作,并用在全局存储器barrier上,从而确保AS创建/更新在AS对象用于光线追踪之前完成更新。
为了最大化重叠,建议为多个创建/更新操作分配足够的临时缓冲区内存,并为每个连续操作分配不同的内存区域。分配多少内存取决于应用程序对加速结构创建性能的敏感程度,以及使用的加速结构量。
更多关于加速结构API的信息,请访问这个页面。
5. 光线追踪shader domain
为了向Vulkan API公开(expose)光线追踪功能,我们定义一组新的shader domain,以及用于着色器间通信的基元。图5说明了流程。
图5:光线追踪shader domain及其关系。
5.1 光线生成着色器(Ray Generation Shader)
光线生成着色器开始所有的光线追踪工作。光线生成着色器在线程的2D网格上运行,非常类似于计算着色器,并且是将追踪进入场景光线的起点。它同时负责将光线追踪算法的最终输出写入至内存。
5.2 相交着色器(Intersection Shader)
相交着色器实现任意光线-基元相交算法。对于支持应用程序将光线与不具备内置支持的不同类型基元(如球体)相交,它们是否有用。三角形基元具备内置支持,不需要相交着色器。
5.3 击中着色器(Hit Shader)
当发现光线-基元相交时,将调用击中着色器。它们负责计算在交叉点处发生的交互(如图形应用程序的光线-材质交互),并且可以根据需要产生新的光线。
有两种击中着色器:对于光线以任意顺序与场景基元的所有相交,调用任意击中着色器(any-hit shader),除了计算着色数据之外,它同时可以拒绝相交。最接近击中着色器(Closest-hit Shader)仅在沿光线的最接近相交点上调用。
5.4 未击中着色器(Miss Shader)
当给定光线没有相交时,调用未击中着色器Inter-shader communication
5.5 着色器间通信
在执行光线追踪调用期间,各个着色器阶段需要在彼此之间传递数据。相交着色器需要输出相交数据,击中着色器需要消耗相交数据并修改一定的任意结果数据,而光线生成着色器则需要使用最终结果数据并将其输出到内存。
光线有效负载是一种任意的,由应用程序定义的数据结构,用于存储沿光线路径累积的信息。它由启动光线查询操作的着色器(通常是光线生成着色器)初始化,并且可以由击中着色器读取和修改。它通常用于输出沿光线路径累积的材质属性,然后由光线生成着色器将其写入内存。
光线属性包含从相交着色器传回至击中着色器的一组值,包含应用程序需要从相交点测试发出的任何数据。它们封装在一个由应用程序定义的结构中,由相交着色器编写,并由该交集点调用的击中着色器读取。对于内置的光线-三角形相交着色器,光线属性是包含沿三角形相交点的重心坐标的vec2。
5.6 GLSL语言映射
相对来说,在Vulkan中启用光线追踪只需要更少的着色语言改动。
API定义了五个用于创建GLSL着色器的新着色器阶段:用于光线生成着色器的rgen,用于相交着色器的rint,用于最接近击中着色器的rchit,用于任意击中着色器的rahit,以及用于未击中着色器的rmiss。
目前存在数个新的内置变量,具体取决于着色器阶段,它们包含有关各种参数的信息。这包括当前的光线生成线程ID,基元和实例ID值,当前光线原点,世界和对象空间中的方向,以及沿光线处理的当前相交的T值等等。
为加速结构资源绑定定义一个新的不透明资源类型:accelerationStructureNVX。加速结构以与其他资源类型相同的方式绑定至着色器管道。注意,可以在同一着色器中使用多个加速结构,从而在着色器中对不同的几何数据集进行分层遍历。
我们同时为用户定义的类型定义了一些新的存储限定符:rayPayloadNVX,rayPayloadInNVX和hitAttributeNVX,它们定义了要使用的光线有效负载(并定义了哪个着色器阶段拥有给定有效负载的存储)和击中属性类型。增加了一个新的布局限定符shaderRecordNVX,它定义了一个绑定至Shader Binding Table的SSBO。
最后,增加了一系列新的内置函数。这包括将光线发射至场景的traceNVX();丢弃一个给定相交点的ignoreIntersectionNVX();以及在击中点终止处理光线的terminateRayNVX()。
5.6.1 光线有效负载结构匹配
光线追踪着色器API允许将任何应用程序定义的结构用作光线有效负载。这个有效负载是在可以生成光线的着色器阶段进行定义,并通过引用修改它的击中着色器阶段传入。
因为GLSL不具有类型多态性,所以我们不能将traceNVX()调用定义为类型多态。这意味着我们不能将任意类型作为参数传递。针对这个问题,我们设计了一个基于位置布局限定符来匹配数据类型的解决方案。
每个光线有效负载结构都使用rayPayloadNVX限定符,以及一个将其与整数值相关联的位置布局限定符进行定义。这个整数值基本上成为有效负载变量的数字标识符,然后传入traceNVX()调用以代替对实际变量的引用。击中着色器需要使用rayPayloadInNVX限定符定义与变量相同的数据类型,从而令光线追踪基础结构能够将变量视为对输入的有效负载数据的引用。
为了说明这个机制,下面是一个简单光线生成着色器的代码片段:
你可以看到primaryPayload是我们的光线有效负载。它在定义中赋予了1的位置索引,而且该索引是作为traceNVX()的最后一个参数传入。
对于相应的最接近击中着色器,它看起来可能是这样:
可以通过rayPayloadInNVX关键字定义相同的有效负载结构。着色器中可能只存在一个rayPayloadInNVX变量(并且只有击中着色器和未击中着色器可以定义它们),因此不需要布局限定符。请注意,匹配的有效负载类型不存在静态验证,不匹配的类型将导致不确定的行为。另外,traceNVX()调用中的有效负载值必须是编译期间的立即值。
更多关于GLSL语言映射的信息,请访问这个页面
6. Shader Binding Table
API介绍的最后一个新概念是Shader Binding Table(SBT)。这是一个VkBuffer,包含一组统一大小的记录,由后面紧跟着应用程序定义数据的着色器句柄组成。着色器句柄确定哪个着色器为给定SBT记录运行,同时记录中的应用程序定义数据可作为SSBO提供给着色器。
每个SBT实例都有固定的记录大小。在每条记录中,一系列的索引规则确定光线追踪基础结构将如何获取下一个着色器要执行的句柄和SSBO数据。
SBT中的SSBO数据旨在指定每个着色器将使用的资源(纹理,统一缓冲区等)。预期的使用模式是,所有潜在的可访问资源通过描述符集绑定至管道。然后,SBT将包含数组中的索引,并指定应该为给定的着色器集使用哪些特定资源。也就是说,从着色器的角度来看,SBT只是“数据”,它也可以包含其他类型的信息。
更多关于Shader Binding Table的信息,请访问这个页面。
7. 光线追踪管道
所需的最后一块拼图是光线追踪管道状态对象。光线追踪管道仅包含一个(可能非常大)光线生成着色器,相交着色器,击中着色器和未击中着色器的集合,以及一定的光线追踪特定参数(如最大光线递归深度)。
由于单个光线追踪PSO可以包含许多着色器,因此编译可能非常耗时。通过添加一个接口来允许应用程序控制各个着色器的编译,我们可以降低这方面的成本。在PSO创建时,VK_PIPELINE_CREATE_DEFER_COMPILE_BIT_NVX这个新flag可以指示驱动程序避免立即编译着色器。然后,应用程序必须为每个着色器调用vkCompileDeferredNVX,以便触发编译工作。这可以跨线程并行化,从而最大限度地缩短编译时间。
在创建后,可以使用包含新管道绑定点VK_PIPELINE_BIND_POINT_RAYTRACING_NVX的标准Vulkan调用来将光线追踪管道对象绑定到图形或计算队列。
更多关于光线追踪管道创建的信息,请访问这个页面。
8. 启动工作
在创建加速结构和Shader Binding Table,以及创建和绑定光线追踪PSO后,现在是时候开始追踪光线了。
单个Vulkan命令启动光线追踪工作:vkCmdTraceRaysNVX。这个命令的参数指定2D线程网格尺寸,以及要使用的包含SBT的VkBuffer,以及该SBT中用于各种数据元素的偏移量(光线生成着色器句柄和SSBO数据偏移量,初始击中着色器句柄和SSBO数据偏移量,以及未击中着色器句柄和相应SSBO数据的偏移量)。
更多关于vkCmdTraceRaysNVX的信息,请访问这个页面。
9. 总结
英伟达已经将VK_NVX_raytracing作为开发者预览版发布,支持开发者熟悉Vulkan中基于RTX的光线追踪。这可以与LunarG的最新Vulkan SDK一起使用,它支持英伟达所有的图灵扩展,允许你通过Vulkan开发光线追踪的应用程序。
英伟达表示,他们相信Vulkan为这个功能提供了良好的基础。API内置的可扩展性意味着我们可以利用大多数现有的机制和基础架构,可实现与现有光栅化功能的无缝集成。