本文介紹了在遊戲和其餘實時圖形應用程序中實現光線跟蹤的最佳實踐。咱們儘量簡短地介紹這些,以幫助您快速找到關鍵的想法。這是基於英偉達工程師在2019年GDC上所作的陳述。數組
經過修剪和選擇性更新,優化加速結構(BLAS/TLAS)構建/更新最多須要2ms。 緩存
去噪RT效果相當重要。咱們已經用NVIDIA RTX Deniser SDK打包了同類產品中最好的Deniser)。 性能優化
使用異步計算隊列將加速結構(BLAS/TLAS)的構建/更新和去噪,與其餘機制(G緩衝區、陰影緩衝區、物理模擬)重疊。 網絡
儘量利用硬件加速進行遍歷。 數據結構
最少的光線投射應該被稱爲「RT開啓」,而且應該提供比光柵化明顯更好的圖像質量。提升質量水平應能以公平的速度提升圖像質量和性能。見下表:異步
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的形式完成的,那麼每次更新都會假設腳很近。在行走/跑步動畫中,這可能會影響跟蹤效率。這裏的一個解決方案是創建幾個關鍵姿式的加速度結構,而後使用最接近的匹配做爲從新裝配的來源。建議採用實驗指導的流程/工藝。
對全部靜態幾何圖形使用壓縮。壓縮速度很快,一般能夠回收大量內存。當對壓縮加速度結構跟蹤光線時,性能沒有降低。
從下表中選擇一個組合開始…
而後考慮添加這些標誌:
ALLOW_COMPACTION。對全部靜態幾何體執行此操做一般是一個好主意,以回收(潛在的)大量內存。
對於可更新的幾何體,壓縮那些具備較長生存期的blas是有意義的,所以額外的步驟是值得的(壓縮和更新不是互斥的!)。
對於每幀都重建(而不是更新)的徹底動態幾何體,使用壓縮一般沒有好處。
不使用壓縮的一個潛在緣由是利用BLAS存儲需求隨原始計數單調增長的保證—這在壓縮上下文中不成立。
MINIMIZE_MEMORY (DXR) / LOW_MEMORY_BIT (VK)。僅當應用程序在如此大的內存壓力下,若是不盡量優化內存消耗,光線跟蹤路徑將不可行時才使用。此標誌一般會犧牲構建和跟蹤性能。並不是全部狀況下都是這樣,但要注意,將來的驅動程序版本可能會有不一樣的行爲,所以不要依賴實驗數據「確認」標誌不會下降性能。
避免在關鍵路徑上建立狀態對象。集合和管道編譯可能須要幾十到幾百毫秒。所以,應用程序應該預先建立全部pso(例如,在level load),或者在後臺線程上異步建立狀態對象,並在準備就緒時進行熱交換。
考慮使用多個光線跟蹤管道(狀態對象)。當跟蹤幾種類型的光線(例如陰影和反射),其中一種類型(陰影)具備幾個簡單的着色器、小的有效載荷和/或較低的寄存器壓力,而另外一種類型(反射)涉及許多複雜的着色器和/或較大的有效載荷時,這尤爲適用。將這些狀況分爲不一樣的管道有助於驅動程序更高效地調度着色器執行,並在更高的佔用率下運行工做負載。
將負載和屬性大小設置爲可能的最小值。爲MaxPayloadSizeInBytes和MaxAttributeSizeInBytes配置的值直接影響寄存器壓力,所以不要將它們設置得高於應用程序/管道絕對須要的值。
將最大跟蹤遞歸深度設置爲可能的最小值。跟蹤遞歸深度影響爲DispatchRays啓動分配的堆棧內存量。這會對內存消耗和整體性能產生很大影響。
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的調度生效。英偉達不斷改進咱們的日程安排。
對場景全局資源使用全局根簽名(DXR)或全局資源綁定(VK)。這將避免在本地每幾何體根表中進行復制,並應致使更好的緩存行爲。
避免資源臨時性。這一般會致使非原語代碼重複。例如,將一個紋理保留在一個臨時的位置,並根據某些條件對其進行指定,將致使每一個可能的紋理指定的全部採樣操做重複。可能的解決方法:使用資源數組並動態索引到其中。
同時訪問64位或128位對齊的本地根表數據能夠實現矢量化加載。
對於對齊的原始數據,首選StructuredBuffer而不是ByteAddressBuffer。
使用RTX去噪SDK實現高質量、快速的光線跟蹤效果去噪。您能夠在GameWorks光線跟蹤頁面找到更多詳細信息。
對於DXR,將QueryVideoMemory API報告的預算視爲軟提示。實際節段大小大約大20%。
將命令分配器隔離到不一樣類型的命令列表。若是能夠避免,不要將非DXR-CAs與DXR-CAs混合和匹配。
命令分配器重置將不會釋放關聯的內存。可使用destroy/create釋放這些分配,但必須在關鍵路徑以外執行此操做,以免長時間暫停
注意管道的堆棧大小。堆棧大小隨着TraceRay調用中保持的活動狀態的數量和TraceRay調用周圍的控制流複雜性而增長。最大跟蹤深度其實是堆棧大小的一個直接乘法器——儘量保持低的深度。
手動管理堆棧(若是適用)。使用API的查詢函數來肯定每一個着色器所需的堆棧大小,並應用關於調用圖的應用程序端知識來減小內存消耗和提升性能。一個很好的例子是在trace depth 1上使用昂貴的反射着色器來拍攝陰影光線(trace depth 2),應用程序知道這些陰影光線只會命中堆棧要求較低的小命中着色器。驅動程序沒法預先知道此調用圖,所以默認的保守堆棧大小計算將過分分配內存。
重用臨時資源。例如,爲BVH構建重用scratch內存資源以用於其餘目的(多是非光線跟蹤)。在DXR上,使用放置的資源和第2層資源堆。
請注意如下工具,其中包括對DirectX光線跟蹤和NVIDIA的VKRay的支持。它們發展很快,因此請確保使用最新版本。
NVIDIA Nsight圖形。爲光線跟蹤開發人員提供了優秀的調試和分析工具(着色器表和資源檢查器、加速結構查看器、範圍分析、扭曲佔用和GPU度量、經過NVIEW後的崩潰調試、C++幀捕獲)。
NVIDIA Nsight系統。提供系統範圍的分析功能和口吃分析功能。
微軟PIX
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緩衝區數據)和驅動程序內部分配(主要是着色器堆棧)。