提示和技巧:光线跟踪最佳实践

提示和技巧:光线跟踪最佳实践

Tips and Tricks: Ray Tracing Best Practices

本文介绍了在游戏和其余实时图形应用程序中实现光线跟踪的最佳实践。咱们尽量简短地介绍这些,以帮助您快速找到关键的想法。这是基于英伟达工程师在2019年GDC上所作的陈述。数组

经过修剪和选择性更新,优化加速结构(BLAS/TLAS)构建/更新最多须要2ms。 缓存

去噪RT效果相当重要。咱们已经用NVIDIA RTX Deniser SDK打包了同类产品中最好的Deniser)。  性能优化

使用异步计算队列将加速结构(BLAS/TLAS)的构建/更新和去噪,与其余机制(G缓冲区、阴影缓冲区、物理模拟)重叠。              网络

尽量利用硬件加速进行遍历。              数据结构

最少的光线投射应该被称为“RT开启”,而且应该提供比光栅化明显更好的图像质量。提升质量水平应能以公平的速度提升图像质量和性能。见下表:异步

Performance Best Practices

1.0      Acceleration Structure Management

1.1 General Practiceside

做为管理(生成/更新)移动到异步计算队列。在图形工做负载中很好地使用异步计算队列对,而且在许多状况下几乎彻底隐藏了成本。相似地,任何AS依赖项(例如蒙皮)也能够移动到异步计算并很好地隐藏。              函数

构建顶级加速结构(TLAS),而不是更新。在大多数状况下,它更容易管理,并且改装的成本节约可能不值得牺牲TLA的质量。              工具

确保GetRaytracingAccelerationStructurePrebuildInfo和BuildRaytracingAccelerationStructure的描述符匹配。不然,分配的缓冲区可能过小,没法容纳AS或scratch内存,可能会产生一个微妙的错误!              oop

不要在TLA中包含skybox/skysphere。在场景中使用天空几何体只会增长光线跟踪时间。改成在Miss着色器中实现天空明暗处理。             

BLAS和TLAS构建之间实现单个屏障。通常来讲,正确性不须要更多的要求。BLAS构建之间的重叠能够在硬件上天然发生,可是添加没必要要的屏障能够序列化该工做的执行。

1.2 Bottom-Level Acceleration Structures (BLAS)

AABBs上使用三角形。RTX图形处理器在加速由三角形几何建立的AS的遍历方面表现突出。             

尽量将几何体标记为不透明。若是几何体不须要执行任何命中着色器代码(例如,对于alpha测试),则始终确保将其标记为不透明,以便尽量有效地使用HW。不透明标志是来自几何体描述符(D3D12_RAYTRACING_geometry_flag_OPAQUE/VK_geometry_OPAQUE_BIT)、实例描述符

D3D12_RAYTRACING_instance_flag_FORCE_OPAQUE/VK_geometry_instance_FORCE_OPAQUE_BIT)仍是经过光线标志(ray_flag_FORCE_OPAQUE/gl_RayFlagsOpaqueNV)并不重要。             

批处理/合并生成/更新调用和几何图形很是重要。最终,在对小批量原语执行AS操做的状况下,GPU将被占用。利用一个构建能够接受多个几何描述符的事实,并在构建时转换几何体。这一般会致使最有效的数据结构,特别是当对象的aabb相互重叠时。将事物分组到BLAS/实例应该遵循空间局部性。不要“把全部有相同材料的东西扔进同一个BLAS中,无论它们最终在太空中的什么地方”。             

知道什么时候更新,而不是(从新)构建。持续更新BLAS会下降其做为空间数据结构的效率,使遍历/交叉查询相对于新构建的查询要慢得多。做为通常规则,应该只考虑动态对象进行更新。若是部分网格相对于其局部邻域的位置发生剧烈变化,则更新后的遍历质量将迅速降低。若是事情只是“弯曲而不是断裂”,那么更新将很是有效。示例:树在风中摇摆:update=good;mesh exploding:update=bad。决定更新或重建蒙皮角色:这取决于。假设最初的构建是以t-pose的形式完成的,那么每次更新都会假设脚很近。在行走/跑步动画中,这可能会影响跟踪效率。这里的一个解决方案是创建几个关键姿式的加速度结构,而后使用最接近的匹配做为从新装配的来源。建议采用实验指导的流程/工艺。             

对全部静态几何图形使用压缩。压缩速度很快,一般能够回收大量内存。当对压缩加速度结构跟踪光线时,性能没有降低。

Use the right build flags.

从下表中选择一个组合开始…

而后考虑添加这些标志:             

ALLOW_COMPACTION。对全部静态几何体执行此操做一般是一个好主意,以回收(潜在的)大量内存。             

对于可更新的几何体,压缩那些具备较长生存期的blas是有意义的,所以额外的步骤是值得的(压缩和更新不是互斥的!)。             

对于每帧都重建(而不是更新)的彻底动态几何体,使用压缩一般没有好处。             

不使用压缩的一个潜在缘由是利用BLAS存储需求随原始计数单调增长的保证—这在压缩上下文中不成立。             

MINIMIZE_MEMORY (DXR) / LOW_MEMORY_BIT (VK)。仅当应用程序在如此大的内存压力下,若是不尽量优化内存消耗,光线跟踪路径将不可行时才使用。此标志一般会牺牲构建和跟踪性能。并不是全部状况下都是这样,但要注意,将来的驱动程序版本可能会有不一样的行为,所以不要依赖实验数据“确认”标志不会下降性能。

2.0 – Ray-Tracing

2.1 – Pipeline Management

避免在关键路径上建立状态对象。集合和管道编译可能须要几十到几百毫秒。所以,应用程序应该预先建立全部pso(例如,在level load),或者在后台线程上异步建立状态对象,并在准备就绪时进行热交换。             

考虑使用多个光线跟踪管道(状态对象)。当跟踪几种类型的光线(例如阴影和反射),其中一种类型(阴影)具备几个简单的着色器、小的有效载荷和/或较低的寄存器压力,而另外一种类型(反射)涉及许多复杂的着色器和/或较大的有效载荷时,这尤为适用。将这些状况分为不一样的管道有助于驱动程序更高效地调度着色器执行,并在更高的占用率下运行工做负载。             

将负载和属性大小设置为可能的最小值。为MaxPayloadSizeInBytes和MaxAttributeSizeInBytes配置的值直接影响寄存器压力,所以不要将它们设置得高于应用程序/管道绝对须要的值。             

将最大跟踪递归深度设置为可能的最小值。跟踪递归深度影响为DispatchRays启动分配的堆栈内存量。这会对内存消耗和整体性能产生很大影响。

2.2 – Shaders

2.2.1 – General

保持射线有效载荷小。有效负载大小转换为寄存器计数,所以直接影响占用率。像打包gbuffer那样打包有效负载一般须要一些数学知识。大量的有效载荷会溢出到记忆中。             

保持低属性计数。与有效负载数据相似,自定义交集着色器的属性转换为寄存器计数,所以应保持最小值。固定函数三角形相交使用两个属性,这是一个很好的准则。             

尽量使用RAY_FLAG_ACCEPT_FIRST_HIT_和_END_SEARCH/gl_RayFlagsTerminateOnFirstHitNV。例如,这一般适用于阴影光线和环境光遮挡光线。请注意,使用此标志比显式调用AcceptHitAndEndSearch()/terminateRayNV()的any hit着色器更有效。             

避免跨跟踪调用的实时状态。一般,在TraceRay调用以前计算并在TraceRay以后使用的变量必须溢出到堆栈中。编译器能够在某些状况下避免这种状况,例如使用从新物质化,但一般溢出是必要的。所以,材质球一开始越能避开它们,效果越好。在某些状况下,当阴影复杂度很是低而且没有递归TraceRay调用时,将一些活动状态放入有效负载中以免溢出是有意义的。然而,这与保持有效载荷小的愿望相冲突,因此要很是明智地使用这个技巧。             

避免着色器中的跟踪调用过多。着色器中的许多TraceRay调用可能会致使次优性能和着色器编译时间。尝试构造代码,使多个跟踪调用合并为一个。             

明智地使用循环展开。若是所述循环包含跟踪调用(前一点的必然结果),则尤为如此。复杂的材质球可能会受到展开循环的影响,而不是从中受益。尝试HLSL中的[loop]属性或GLSL中的显式展开。              

尝试无条件执行TraceRay调用。保持TraceRay调用不使用“if”语句能够帮助编译器简化生成的代码并提升性能。不要使用条件,尝试将光线的tmin和tmax值设置为0以触发未命中,而且(若是须要正确的行为)使用无操做未命中着色器以免意外的反作用。

Use RAY_FLAG_CULL_BACK_FACING_TRIANGLES / gl_RayFlagsCullBackFacingTrianglesNV judiciously。与光栅化不一样,光线跟踪中的背面消隐一般不是性能优化,它能够致使执行更多而不是更少的工做。

2.2.2 – Ray-Generation Shaders

确保每一个光线生成线程生成一个光线。在最终不会产生任何光线的光线生成着色器中调度/分配线程可能会损害调度。这里可能须要人工压实。

2.2.3 – Any Hit Shaders

保持任何击中阴影极简。任何命中着色器在每个TraceRay中执行屡次(与最近的命中或未命中着色器相比,例如,后者只执行一次),这使得它们很是昂贵。此外,任何命中都在调用图中寄存器压力最高的点执行。因此为了得到最佳性能,尽量让它们变得微不足道。

2.2.4 – Shading Execution Divergence

从一个简单的着色实现开始。当实现须要大量材质着色(例如反射或GI)的技术时,性能可能会受到着色散度的限制。这一般有许多缘由,但不限于:指令缓存抖动和/或发散内存访问。采用如下策略来解决这些问题:             

使用简化的着色器优化指令发散:             

使用较低质量或简化的材质球(相对于光栅化)进行光线跟踪。             

在某些极端状况下(例如:漫反射GI或粗糙镜面反射),能够从视觉上接受彻底回落到顶点级别的着色(这也有减小噪声的附加好处)。             

经过如下方法优化发散内存访问:             

下降纹理访问的分辨率-或偏移mip贴图级别             

将光线跟踪着色器中的照明计算推迟到帧中的稍后点             

在极端状况下,可能须要手动安排(分类/装箱)阴影。当上述优化策略不足时,应用程序能够手动安排着色。可是,这会阻止基于driver/HW的调度生效。英伟达不断改进咱们的日程安排。

2.3 – Resources

对场景全局资源使用全局根签名(DXR)或全局资源绑定(VK)。这将避免在本地每几何体根表中进行复制,并应致使更好的缓存行为。             

避免资源临时性。这一般会致使非原语代码重复。例如,将一个纹理保留在一个临时的位置,并根据某些条件对其进行指定,将致使每一个可能的纹理指定的全部采样操做重复。可能的解决方法:使用资源数组并动态索引到其中。             

同时访问64位或128位对齐的本地根表数据能够实现矢量化加载。             

对于对齐的原始数据,首选StructuredBuffer而不是ByteAddressBuffer。

3.0 – Denoisers

使用RTX去噪SDK实现高质量、快速的光线跟踪效果去噪。您能够在GameWorks光线跟踪页面找到更多详细信息。

4.0 – Memory Management

对于DXR,将QueryVideoMemory API报告的预算视为软提示。实际节段大小大约大20%。             

将命令分配器隔离到不一样类型的命令列表。若是能够避免,不要将非DXR-CAs与DXR-CAs混合和匹配。             

命令分配器重置将不会释放关联的内存。可使用destroy/create释放这些分配,但必须在关键路径以外执行此操做,以免长时间暂停             

注意管道的堆栈大小。堆栈大小随着TraceRay调用中保持的活动状态的数量和TraceRay调用周围的控制流复杂性而增长。最大跟踪深度其实是堆栈大小的一个直接乘法器——尽量保持低的深度。             

手动管理堆栈(若是适用)。使用API的查询函数来肯定每一个着色器所需的堆栈大小,并应用关于调用图的应用程序端知识来减小内存消耗和提升性能。一个很好的例子是在trace depth 1上使用昂贵的反射着色器来拍摄阴影光线(trace depth 2),应用程序知道这些阴影光线只会命中堆栈要求较低的小命中着色器。驱动程序没法预先知道此调用图,所以默认的保守堆栈大小计算将过分分配内存。             

重用临时资源。例如,为BVH构建重用scratch内存资源以用于其余目的(多是非光线跟踪)。在DXR上,使用放置的资源和第2层资源堆。

5.0 – Profiling and Debugging

请注意如下工具,其中包括对DirectX光线跟踪和NVIDIA的VKRay的支持。它们发展很快,因此请确保使用最新版本。             

NVIDIA Nsight图形。为光线跟踪开发人员提供了优秀的调试和分析工具(着色器表和资源检查器、加速结构查看器、范围分析、扭曲占用和GPU度量、经过NVIEW后的崩溃调试、C++帧捕获)。             

NVIDIA Nsight系统。提供系统范围的分析功能和口吃分析功能。             

微软PIX

FAQ

Q、 基本体的数量和加速结构构建/更新的成本(时间)之间的关系是什么?

A、 基本上是线性关系。好吧,它开始变得线性超过某个原始计数,在此以前,它被常数开销所限制。这里的确切数字在不断变化,不可靠。             

Q、 假设最大占用率,加速结构构建/更新的GPU吞吐量SOL是多少?             

A、 一个数量级准则是:对于完整构建,O(1亿)原语/秒;对于更新,O(10亿)原语/秒。             

Q、 RT-PSOs的惟一着色器数量与编译成本(时间)之间的关系是什么?             

A、 它大体是线性的。             

Q、 如今游戏中RT-PSO编译的典型成本是多少?             

A、 任何地方,20 ms→300 ms,每条管道。             

Q、 有没有关于应该使用多少alpha/透明度的指导?任何命中和最近命中的代价是什么?             

A、 任何打击都是昂贵的,应该使用最少。最好将几何体(或实例)标记为不透明,这将容许在固定功能硬件中进行光线遍历。当须要AH时(如评估透明度等),尽量简单。不要仅仅为了执行alpha-tex查找和if语句而计算巨大的着色网络。             

Q、 开发人员应该如何管理阴影差别?             

A、 首先在最近的命中着色器中着色,在一个简单的实现中。而后分析perf并决定问题分歧的程度以及如何解决。解决方案可能包括也可能不包括“手动调度”。             

Q、 开发人员如何查询堆栈内存分配?              

A、 API具备在管道/着色器上查询每一个线程堆栈需求的功能。这对于跟踪和分析很是有用,应用程序应该尽量少地使用着色器堆栈(一个建议是在开发期间转储堆栈大小直方图和标记异常值)。堆栈需求最直接地受到跟踪调用的实时状态的影响,这应该最小化(请参阅最佳实践)。。             

Q、 一个典型的光线跟踪实现须要额外消耗多少VRAM?             

A、 今天,实现光线跟踪的游戏一般使用1到2 GB的额外内存。主要影响因素是加速结构资源、光线跟踪特定屏幕大小的缓冲区(扩展g缓冲区数据)和驱动程序内部分配(主要是着色器堆栈)。

相关文章
相关标签/搜索