您的位置 首页 kreess

DXR實時光線跟蹤框架和應用分享

【前言】光線跟蹤是計算從某個點發射光線和物體相交的算法。不像光柵化算法將物體投影進相機,它可以看作反過來從相機或者直接利用物體某點發射光線,光線打到物體上以後可以再次發射,

【前言】

光線跟蹤是計算從某個點發射光線和物體相交的算法。不像光柵化算法將物體投影進相機,它可以看作反過來從相機或者直接利用物體某點發射光線,光線打到物體上以後可以再次發射,通過模擬出物體之間光線的傳播,從而實現更真實的陰影、反射、焦散、環境光遮蔽乃至全局渲染效果。但是由於光線跟蹤計算量大,並行難度大,難以高效集成到硬件上,因此過往一般隻在電影制作等離線場景上使用。

直到2018年,微軟推出DirectX Raytracing(DXR)實時光線跟蹤框架,NVIDIA和UE4在GDC發佈星球大戰實時光線跟蹤演示,隨後NVIDIA發佈瞭Turing顯卡,其搭載硬件加速單元,光線跟蹤這項傳統上隻能離線渲染的方法能夠在單張顯卡上實時跑起來,讓遊戲在畫質上實現一大跨越。

同年,《逆水寒》跟NVIDIA合作,推出中國首款RTX光線跟蹤技術demo,基於光線追蹤的反射,陰影,焦散相比傳統技術更符合物理真實世界,使得光線追蹤有瞭質的突破。同時采用dlss,利用深度學習來達到抗鋸齒的功能。今年六月,光線跟蹤正式在《逆水寒》遊戲中上線,作為一次重大遊戲圖形技術升級,使遊戲更加身臨其境,給廣大玩傢帶來視覺上的升艙體驗。

本篇文章就將從DXR光線跟蹤框架和《逆水寒》光線跟蹤應用兩部分進行闡述。

【DXR 管線】

DXR是一套在GPU上實現光線跟蹤的DirectX框架,它對DirectX作瞭兩部分擴充,API部分添加構建新的PipelineStateObject、創建AccelerateStructure、設置ShaderTable和發射光線DispatchRays,著色器部分添加RayGenShader、IntersectionShader、AnyhitShader、ClosesthitShader、MissShader新的shader類型。

下面將分別介紹著色器部分、DXR擴充API部分:

  • DXR著色器

DXR GPU管線流程圖[1]

RayGenShader:作為DXR發射光線的入口著色器,每個線程以grid形式調用這個著色器,類似計算著色器,每個線程可以通過DispatchRaysIndex獲取grid裡位置,DispatchRaysDimensions獲取grid大小。通過這個著色器調用TraceRay函數來發射光線。

IntersectionShader:當光線與用戶自定義軸對齊包圍盒(簡稱AABB)相交時,DXR管線會調用這個著色器,這個著色器的作用是在用戶定義幾何體(比如過程性建模)的是否相交的判斷以這個著色器形式提供給用戶,當物體是三角形時,並不需要這個函數,DXR內建三角形求交方法,在光線擊中三角形構成的包圍盒時不會調用這個著色器。

AnyhitShader:這個著色器會在光線與三角形或者用戶定義的幾何體發生相交時被調用,通過調用IgnoreHit函數告訴DXR GPU管線忽視這次相交繼續尋找新的相交點,或者調用AcceptHitAndEndSearch函數結束尋找求交,或者不調用上面兩個函數那麼DXR接受這次相交並繼續尋找更近的新的相交點,註意DXR GPU管線調用這個著色器並不是沿光線方向有序的,其中一個使用場景是透明陰影,由於陰影隻要有無相交即可判斷,不要求有序。一般情況下不需要這個著色器,這樣能夠提升性能。

ClosesthitShader: 當DXR管線求交到最近的交點或者AnyhitShader裡調用AcceptHitAndEndSearch函數時的交點會調用這個著色器,光線相交後物體渲染用這個著色器來處理。

MissShader: 當光線沒有與物體相交時就會調用這個著色器,這個著色器用來相交失敗時處理,比如計算天空盒。

上述著色器在訪問參數時除瞭接收從DX12 CommandList設置資源綁定外,還能夠從ShaderTable裡讀取裡面資源綁定,DX12 CommandList資源綁定,會被用於DXR流程裡各個著色器,被稱為全局根簽名,而從ShaderTable讀取的能夠實現著色器與資源綁定,實現資源綁定實例化,這部分被稱為本地根簽名。

在光線與物體相交後DXR GPU管線會把物體相交的三角形編號和在三角形重心坐標系下交點位置傳入ClosesthitShader和AnyHitShader,通過讀取本地根簽名綁定的頂點數據,還原出交點的各種頂點和材質屬性用於渲染,在ClosestHitShader和MissShader除瞭作渲染計算外,還可以再次發射光線,實現光線多次反彈,實現復雜全局效果。

  • DXR API

1、DXR的管線狀態對象

跟DX12創建管線狀態對象流程不同,DXR的管線狀態對象是一個StateObject,StateObject由一系列SubObject組成,每個SubObject包含一種對管線對象配置,DXR的管線對象一般需要下面幾種SubObject類型:

D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY:指定著色器編譯成Library類型DXIL字節碼,並設置需要哪些著色器的入口函數導出。

D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE:用於指定全局根簽名。

D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE:用於指定本地根簽名。

D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP:將導出的IntersectionShader、AnyhitShader、ClosesthitShader組成HitGroup,設置HitGroup導出名字。

D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION:將SubObject與導出著色器相關聯,把像全局根簽名、本地根簽名這類配置應用於著色器。

D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG:設置用戶定義變量Payload和著色器傳入Attribute的最大大小,這個配置可以通過D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION來運用特定的著色器,但是對於同一個PSO內每個著色器的配置必須一致。

D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG:設置光線發射的最大遞歸次數,可用於特定著色器,但是對PSO每個著色器配置要一致。

增大遞歸次數或者Payload和Attribute的大小,會導致占用更多register/memory資源,可能會影響性能,按實際需要來指定大小。

這樣基本配置完後通過CreateStateObject函數創建出DXR管線狀態對象。

2、DXR的加速結構

DXR光線跟蹤使用一種不透明結構來加速光線與物體的求交,這個加速結構分成BottomLevel和TopLevel兩層,BottomLevel加速結構負責管理三角形網格或者軸對稱包圍盒,TopLevel加速結構引用BottomLevel加速結構,通過兩級結構,使得BottomLevel能夠被TopLevel加速結構下多個實例引用,在不改變BottomLevel加速結構下實現坐標變換,多實例化,快速更新動態場景,同時節省資源開銷。

創建BottomLevel加速結構,需要三角形網格的頂點索引數據,或者AABB數據,有個Transform屬性可以用來在創建加速結構時變化頂點的位置,設置相關輸入參數,需要通過

GetRaytracingAccelerationStructurePrebuildInfo函數來獲取創建加速結構的buffer大小,在分配好buffer後,通過BuildRaytracingAccelerationStructure創建。

創建TopLevel加速結構,TopLevel加速結構有多個實例組成,每個實例需要設置它的BottomLevel加速結構、坐標Transform和實例的各個屬性,是通過D3D12_RAYTRACING_INSTANCE_DESC來描述。實例的屬性包括:

InstanceID:設置InstanceID,可以在HitGroup著色器裡獲取對應實例InstanceID。

InstanceMask:指定掩碼,在光線相交時跟TraceRay函數傳入參數作與計算,來判斷是否跳過這次相交,這樣可以讓光線跟特定物體相交,比如物體是否參與陰影或者反射。

InstanceContributionToHitGroupIndex:光線與物體相交後會計算ShaderTable索引位置,這個參數會參與這個計算。

DXR 加速結構的結構關系和ShaderTable的索引關系[1]

3、DXR ShaderTable

ShaderTable是一個存儲瞭ShaderRecord結構體數組的Buffer,ShaderRecord包含瞭ShaderId和Shader的本地資源綁定,在光線相交時尋找ShaderTable對應ShaderRecord,調用對應著色器並傳入全局資源綁定和本地資源綁定的參數。DXR管線是這樣尋找ShaderRecord,對於HitGroup,ShaderRecord的位置由調用DispatchRay的傳入參數HitGroupTable.StartAddress和HitGroupTable.StrideInBytes、TraceRay參數RayContributionToHitGroupIndex和MultiplierForGeometryContributionToHitGroupIndex、TopLevel加速結構裡InstanceContributionToHitGroupIndex,BottomLevel加速結構的傳入幾何體位置GeometryContributionToHitGroupIndex所決定:

HitGroupTable.StartAddress + HitGroupTable.StrideInBytes * (RayContributionToHitGroupIndex + MultiplierForGeometryContributionToHitGroupIndex * GeometryContributionToHitGroupIndex) + InstanceContributionToHitGroupIndex

對於MissShader,ShaderRecord的位置由調用DispatchRay的傳入參數MissShaderTable.StartAddress和MissShaderTable.StrideInBytes、TraceRay參數MissShaderIndex決定,關系如下

MissShaderTable.StartAddress + MissShaderTable.StrideInBytes * MissShaderIndex

設置好ShaderTable後,使用DispatchRays函數來調用RayGenShader,RayGenShader則發射光線,按照DXR GPU管線步驟,調用相應著色器完成一輪光線跟蹤的渲染。

【逆水寒DXR光線跟蹤管線】

逆水寒DXR光線跟蹤使用混合光柵化渲染管線,在GBuffer計算與LightPass之間插入Raytracing流程,直接利用GBuffer信息,跳過攝像機與物體求交,直接在物體表面上發射多條光線,實現陰影、反射、焦散效果。

  • RT軟陰影

陰影是光源發出光線被物體遮蔽後產生黑暗區域,區域分為本影區和半影區,本影區是光源完全被物體遮蔽區域,而半影區是部分被物體遮蔽區域,傳統ShadowMap方法隻能記錄光的視角下遮蔽信息,這種隻能產生本影區,不能計算半影區的陰影值,而且由於ShadowMap精度問題會出現ShadowAcne等問題,而利用光線跟蹤利用陰影可見性公式直接計算出任意光源下任意物體遮蔽下產生的真實陰影。

V=∫ v(Ω)dΩ

上面是一個積分,用MonteCarlo積分將連續性計算轉變成離散采樣問題,隨機發射一條光線,記錄下是否被物體擋住,然後除以概率分佈。

V= 1/n ∑_1^n v(Ω_i )/PDF(Ω_i)dΩ_i

由於隨機采樣,所以每個樣本不斷抖動,但是隨著樣本越多,抖動就越少,當收集到足夠的樣本時結果收斂到期望結果,但是由於樣本數目跟計算時間成正比,所以較少樣本生成高質量結果比較重要,其中低差異序列[2]產生隨機數能夠更快地樣本收斂至期望值,被廣泛用於光線跟蹤中。

由於光線的方向可以立體角表示,需要用二維的隨機數來表示這個光線方向,這裡需要根據不同光源來建立均勻隨機數到對於光源來說均勻的光線方向的映射關系,對於點光源,由於它沒有體積這樣不會有軟陰影,所以在逆水寒實現裡是把點光源看作帶體積的球狀光源,將體積作為參數,供美術來調節軟硬程度。

cos⁡Θ=x

ϕ=2πy

(點光源的隨機數映射公式)

目前光線跟蹤要做到實時,每個像素的光線是比較有限的,一般一個像素一條光線(1spp),在1spp下顯然渲染出來的陰影充滿噪點,為瞭能夠在很低樣本下得到比較好的結果,學術界和工業界提出一系列除噪算法,像SVGF[3]、NVIDIA RTX Shadow Denoiser[4]之類能夠在1spp下完成除噪,這些算法的基本原理是復用樣本,增大有效樣本數目來降低噪聲,首先空間上使用卷積的方法,比如用高斯核收集周邊的樣本,但是這樣會把圖像模糊化,所以需要像SVGF通過自適應核控制核的寬度或者RTX Shadow Denoiser代替圖像空間使用世界空間核,對核的長寬方向進行優化,來避免模糊的現象,然後在時間上使用類似TAA的方法收集樣本,對於靜態場景時間樣本是可靠的,所以一邊收集一邊調整空間核的大小,進一步提高除噪效果,但是運動場景TAA找不到正確的運動向量,會出現鬼影問題,就需要一系列類似neighbor clamping方法來除去鬼影。

未除噪的陰影效果 空間除噪效果加上時間累積後除噪效果

  • RT反射

傳統光柵化管線的反射效果[5],一般采用屏幕空間反射、平面反射,屏幕空間是用raymarching方法找到在GBuffer裡交點,能夠很清晰地表現鏡面反射的圖像,由於使用GBuffer信息,在屏幕外或者物體在屏幕上不可見部分就沒法顯示出來;平面反射鏡像地繪制一邊場景,但存在每個平面都需要渲染場景一遍開銷大,而且隻能夠在平面上反射問題,其它使用Voxel Cone Tracing[6]能夠在任意物體表面上作反射,但是存在精度低,隻能用作鏡面模糊反射上。光線跟蹤能夠清晰地反射屏幕外物體,配合顯卡硬件加速單元,實現實時任意物體上作反射效果。

SSR反射效果光追反射效果

不同物體都有不同材質,當光線擊中物體後,需要在ClosestHitShader中計算材質渲染,材質渲染在光柵化在Pixel Shader裡處理,它接收Vertex Shader解出來GBuffer裡物體屬性,而在RT反射中沒有GBuffer而是擊中物體的位置,需要一個函數將擊中物體位置相關信息計算出Pixel Shader所需的各種物體屬性,用統一函數去計算擊中點的位置,讀取物體的VBIB屬性,計算出物體位置,讀取所需要的物體屬性,再調用Pixel Shader裡面函數,完成物體的材質渲染。

對於不透明物體上述就處理好瞭,但是對於半透明物體或者需要Alphatest的物體(比如樹葉),需要模擬光柵化管線的Alphatest和Alphablend流程,實現中將渲染分成兩個部分:不透明渲染、半透明渲染,物體也根據參與哪種渲染分成兩組,對於不透明渲染,沿著光的方向不斷與不透明渲染的物體求交,當物體的alpha值大於閾值就停止並輸出結果,對於半透明渲染,在沿著光的方向不斷與半透明的物體求交,記錄下物體的渲染的結果和Alphablend的類型,當物體的Alpha值也大於一定閾值或者光線長度等於不透明渲染時結果輸出的長度時停止,然後從後往前依次根據Alphablend類型作混合,這樣得到反射圖像的渲染效果與光柵化管線渲染物體保證一致,然後限制物體的粗糙度,隻有小於某個物體粗糙度閾值才發射光線,避免光線開銷。

  • RT焦散

當光線打到光澤表面反射到粗糙表面時,由於光澤表面的不規則導致,存在多條光線匯聚到粗糙表面上某個點,出現明亮不規則的紋路,這個就是焦散效果。從被照亮物體出發尋找有效的光路在這種情況比較困難,對於這個問題,采用光子映射方法來處理焦散,光子映射做法是從光源處發射大量光子,每個光子攜帶能量值,光子經過光澤表面反射打到粗糙表面,計算粗糙表面下的光子能量分佈。

在逆水寒實現,采用GDC上水面焦散[7]的方法的變種,先以太陽光視角記錄水面網格的位置和法線方向,生成一張CausticsMap,然後從CausticsMap所記錄的水面網格位置出發,沿著反射光線的方向作光線跟蹤,將打到物體的焦點投影到屏幕空間,記錄下屏幕空間下每個光子的數目,乘上供美術調節的顏色換算值,得到焦散貼圖,供後續光照部分計算使用。

由於光子的數目有限,焦散貼圖存在顆粒狀分佈,就需要除噪,使用帶權重均勻核作個卷積計算就可以得到較好效果。

未除噪焦散效果除噪後焦散效果

光線跟蹤版本陰影、反射、焦散加入,明顯提高三種渲染的效果,上述三種光線跟蹤效果隻是在遊戲裡應用光線跟蹤的開始。隨著GPU性能提升,接下來還有很多工作在探索和繼續進行中,像基於物理模型的反射,多次反射和折射,乃至全局光照,都將在未來一步一步提高光線跟蹤場景下質感。

參考資料

[1] https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html

[2] https://en.wikipedia.org/wiki/Low-discrepancy_sequence

[3] Spatiotemporal Variance-Guided Filtering: Real-Time Reconstruction for Path-Traced Global Illumination

[4] https://developer.nvidia.com/gameworks-ray-tracing

[5] https://www.gdcvault.com/play/1020770/Taking-Killzone-Shadow-Fall-Image

[6] https://developer.nvidia.com/vxgi

[7] Ray-Guided Volumetric Water Caustics in Single Scattering Media with DXR, by Holger Gruen

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

返回顶部