DXR 實時光線追蹤技術概覽
長久以來“光柵化(rasterization)”一直統治著實時渲染領域,其實並不是說這種渲染方式有多麼的好,主要是因為它更便於實現硬體加速。光柵化渲染的基本演算法很簡單,但是要想達到好的渲染效果,就需要不斷打補丁。你想要陰影嗎?那你需要一種特殊的演算法實現。你想要反射、折射嗎?每個都需要一個特殊的方式去實現,更不要提全域性照明瞭。這些不禁讓我想起早些年“固定渲染管線”顯示卡發展到鼎盛的時候,圖形程式設計師不得不調整各種引數新增 Environment Map、Bump Map等等,後來“可程式設計渲染管線”推出,那些 tricky 的做法都沒必要了。而現在 GPU 越來越強大,效能在不斷提高,也更具通用計算能力。另一方面 low level 圖形 API 的出現也讓圖形程式設計師有更多的想象空間去控制 GPU,這一切都在將實時渲染技術推向下一代:Real-time ray tracing。這可是幾十年來實時渲染、遊戲渲染技術領域最大的進步!
NVIDIA 已經在 8 月份的 SIGGRAPH 2018 大會上釋出了新的圖靈架構 GPU,新一代消費級 GeForce 顯示卡也已經呼之欲出!新架構的最大亮點無疑是支援 Real-time ray tracing 硬體加速以及使用 AI 技術對光線追蹤的計算結果進行降噪!下面這篇文章就主要介紹一下 DirectX Ray Tracing 定義的這個渲染管線長什麼樣。
DirectX Raytracing(以下簡稱 DXR )並不是一套全新的 API,而是在 DirectX 12 的一個新的 feature。它主要引入了這麼四個新的東西:
- Acceleration Structure
- 一個新的 Command List 方法:DispatchRays ,它用來了啟動 Ray Tracing 的渲染流程;
- 一系列在 Ray Tracing Pipeline 過程中的 Shader 型別:
- Ray generation shader
- Intersection shader
- Miss shader
- Closest-hit shader
- Any-hit shader
- Raytracing pipeline state
下面就其中的重點部分做一個詳細的介紹。
Acceleration Structure
顧名思義,為了加速光線與場景的相交運算,需要把場景中的幾何體用一種特殊的方式組織,一般是某種空間分割演算法處理後的結果,在 DXR 裡面這個資料結構就被稱為 “Acceleration Structure”. 這個資料結構由 DXR 負責生成,通過呼叫 BuildRaytracingAccelerationStructure() 函式。
這個資料結構分為兩層:
- Top-Level acceleration structures 是物件例項級別的,每個例項可以包含一個 Transform 矩陣。
- Bottom-level acceleration structures 是集合體級別的,對於傳統的三角形 Mesh,它使用 bounding volume hierarchy (BVH) 樹形資料結構來管理這些三角形。
場景中的動畫更新時,一般更新 Top-Level hierarchy 就可以了,所以效率是很高的。今年的 SIGGRAPH 還有一個相關的 Paper,有興趣的話可以看看:MergeTree: A Fast Hardware HLBVH Constructor for Animated Ray Tracing。
Acceleration Structure 的兩個層級(圖片來自 NVIDIA)
DXR Shaders
因為 Ray tracing 的計算過程和光柵化是完全不同的,所以 vertex shader, geometry shader, pixel shader 那些就都可以撇到一邊兒了!DXR 引入了一系列新的 Shader 型別,上面已經列出了。下面就通過一個最簡單的例子,來體會一些這些 Shader 型別吧。下面這段 Shader 程式碼完成了一個最簡單的 Ray tracing 的渲染:有模型的地方渲染成紅色,沒有模型的地方把背景設定為藍色。
RWTexture<float4> gOutTex;
struct RayPayload {
float3 color;
};
[shader(“miss”)]
void MyMiss(inout RayPayload payload) {
payload.color = float3( 0, 0, 1 );
}
[shader(“closesthit”)]
void MyClosestHit(inout RayPayload data, BuiltinIntersectAttribs attribs) {
data.color = float3( 1, 0, 0 );
}
[shader(“raygeneration”)]
void MyRayGen() {
uint2 curPixel = DispatchRaysIndex().xy;
float3 pixelRayDir = normalize( getRayDirFromPixelID( curPixel ) );
RayDesc ray = {
gCamera.posW,
0.0f,
pixelRayDir,
1e+38f
};
RayPayload payload = {
float3(0, 0, 0)
};
TraceRay( gRtScene, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload );
outTex[curPixel] = float4( payload.color, 1.0f );
}
(程式碼來自 Chris Wyman, NVIDIA)
下面就具體看一下這段程式碼中的幾個函式:
- MyRayGen()
這個函式是一個 Ray generation shader,當 DispatchRays() API 被呼叫後會自動啟動,它有點像咱們 C 語言中的 main() 函式,控制整個 Ray tracing 的流程。一般情況下,它會使用新增的內建 shader 函式 TraceRay() 進行光線追蹤的計算,計算結果會返回到最後一個引數中,即上面例子中的 RayPayload payload。這個結構應該包含一個顏色值,作為著色計算的結果。最後,把這個結果寫入到 Render Target中。 - MyClosestHit()
它的型別是 Closest-hit shader,這是執行材質 Shading 的地方。值得注意的是,你在這個 Shader 中還是可以呼叫 TraceRay() 函式進行遞迴的光線追蹤,也可以呼叫很多次 TraceRay() 來通過蒙特卡洛方法去計算 AO 等,總之這個是非常靈活的。 - MyMiss()
它的型別是 Miss shader,顧名思義,當光線沒有和場景中的任何幾何體產生碰撞的時候,這個 Shader 會被呼叫。這個 Shader 一般用來實現背景的繪製,例如天空等。
我們還有兩個型別的 Shader 沒有涉及到:
- Intersection shader
這個 Shader 是用來定義光線和場景的相交計算的。系統預設提供了光線與三角形 Mesh 的相交運算,如果你需要檢測與球體、引數化曲面等特殊計算的時候,可以通過這個 Shader 進行自定義。 - Any-hit shader
這是一個疑問句:Any hit?當光線和幾何體產生了碰撞的時候,會呼叫這個 Shader 來詢問是否真的產生了 hit。這個一般用來實現 Alpha Mask 效果。
DXR Pipelline
The ray tracing pipeline(圖片來自 Chris Wyman, NVIDIA)
在瞭解了上述的 Shader 各自負責的計算之後,通過上圖就可以很直觀的瞭解整個 DXR Pipeline 的流程了。其中綠色的部分,是系統實現的,是可以被 GPU 加速的,我們可以通過 API 去控制。藍色的部分是可以通過 Shader 程式設計去實現的。上面列出的那些 Shader 就是一條光線所需的計算流程,這個流程在 GPU 中會並行執行。這些 Shader 之間可以通過 ray playload 結構體資料進行通訊,這是一個使用者自定義的資料結構。它作為一個 inout 引數傳遞給 TraceRay() 函式,在 Closest-hit shader 和 Miss shader 都可以對它進行修改。
結束語
Real-time ray tracing 是不是讓你無比振奮啊!是不是已經想躍躍欲試了呢?如果你對圖形技術有極高的熱情、有信心挑戰實時渲染領域的前沿技術,那麼現在有一個很好機會再召喚你!螞蟻金服·圖形與數字藝術實驗室正在招聘圖形開發專家,美術設計師,期待各路達人加入!可以直接聯絡博主本人,傳送簡歷到 neil3d (at) 126.com 。這是一個有誠意的招聘貼,求轉發!具體的職位列表和職位描述請見這個語雀文件。