1. rendering path 的技術基礎
在介紹各種光照渲染方式之前,首先必須介紹一下現代的圖形渲染管線。這是下面提到的幾種 Rendering Path 的技術基礎。
目前主流的游戲和圖形渲染引擎,包括底層的 API(如 DirectX 和 OpenGL)都開始支持現代的圖形渲染管線。現代的渲染管線也稱為可編程管線(Programmable Pipeline),簡單點說就是將以前固定管線寫死的部分(比如頂點的處理,像素顏色的處理等等)變成在 GPU上可以進行用戶自定義編程的部分,好處就是用戶可以自由發揮的空間增大,缺點就是必須用戶自己實現很多功能。
下面簡單介紹下可編程管線的流程。以 OpenGL 繪制一個三角形舉例。首先用戶指定三個頂點傳給 Vertex Shader。然后用戶可以選擇是否進行 Tessellation Shader(曲面細分可能會用到)和 Geometry Shader(可以在 GPU 上增刪幾何信息)。緊接著進行光柵化,再將光柵化后的結果傳給 Fragment Shader 進行 pixel 級別的處理。最后將處理的像素傳給FrameBuffer 并顯示到屏幕上。
2. 幾種常用的 Rendering Path
Rendering Path 其實指的就是渲染場景中光照的方式。由于場景中的光源可能很多,甚至是動態的光源。所以怎么在速度和效果上達到一個最好的結果確實很困難。以當今的顯卡發展為契機,人們才衍生出了這么多的 Rendering Path 來處理各種光照。
2.1 Forward Rendering
Forward Rendering 是絕大數引擎都含有的一種渲染方式。要使用 Forward Rendering,一般在 Vertex Shader 或 Fragment Shader 階段對每個頂點或每個像素進行光照計算,并且是對每個光源進行計算產生最終結果。下面是 Forward Rendering 的核心偽代碼。
比如在 Unity3D 4.x 引擎中,對于下圖中的圓圈(表示一個 Geometry),進行 Forward Rendering 處理。
將得到下面的處理結果
也就是說,對于 ABCD 四個光源我們在 Fragment Shader 中我們對每個 pixel 處理光照,對于 DEFG 光源我們在 Vertex Shader 中對每個 vertex 處理光照,而對于 GH 光源,我們采用球調和(SH)函數進行處理。
Forward Rendering 優缺點
很明顯,對于 Forward Rendering,光源數量對計算復雜度影響巨大,所以比較適合戶外這種光源較少的場景(一般只有太陽光)。
但是對于多光源,我們使用 Forward Rendering 的效率會極其低下。因為如果在 vertex shader 中計算光照,其復雜度將是O(num_geometry_vertexes ? num_lights),而如果在fragment shader 中計算光照,其復雜度為O(num_geometry_fragments ? num_lights) 。可見光源數目和復雜度是成線性增長的。
對此,我們需要進行必要的優化。比如
1、多在 vertex shader 中進行光照處理,因為有一個幾何體有 10000 個頂點,那么對于 n 個光源,至少要在 vertex shader 中計算 10000n 次。而對于在 fragment shader 中進行處理,這種消耗會更多,因為對于一個普通的 1024x768 屏幕,將近有 8 百萬的像素要處理。所以如果頂點數小于像素個數的話,盡量在 vertex shader 中進行光照。
2、如果要在 fragment shader 中處理光照,我們大可不必對每個光源進行計算時,把所有像素都對該光源進行處理一次。因為每個光源都有其自己的作用區域。比如點光源的作用區域是一個球體,而平行光的作用區域就是整個空間了。對于不在此光照作用區域的像素就不進行處理。但是這樣做的話,CPU 端的負擔將加重。
3、對于某個幾何體,光源對其作用的程度是不同,所以有些作用程度特別小的光源可以不進行考慮。典型的例子就是 Unity 中只考慮重要程度最大的 4 個光源。
2.2 Deferred Rendering
Deferred Rendering(延遲渲染)顧名思義,就是將光照處理這一步驟延遲一段時間再處理。具體做法就是將光照放在已經將三維物體生成二維圖片之后進行處理。也就是說將物空間的光照處理放到了像空間進行處理。要做到這一步,需要一個重要的輔助工具——G-Buffer。G-Buffer 主要是用來存儲每個像素對應的 Position,Normal,Diffuse Color 和其他 Material parameters。根據這些信息,我們就可以在像空間中對每個像素進行光照處理[3]。下面是Deferred Rendering 的核心偽代碼。
下面簡單舉個例子。
首先我們用存儲各種信息的紋理圖。比如下面這張 Depth Buffer,主要是用來確定該像素距離視點的遠近的。
根據反射光的密度/強度分度圖來計算反射效果。
下圖表示法向數據,這個很關鍵。進行光照計算最重要的一組數據。
下圖使用了 Diffuse Color Buffer。
這是使用 Deferred Rendering 最終的結果。
Deferred Rendering 的最大的優勢就是將光源的數目和場景中物體的數目在復雜度層面上完全分開。也就是說場景中不管是一個三角形還是一百萬個三角形,最后的復雜度不會隨光源數目變化而產生巨大變化。從上面的偽代碼可以看出 deferred rendering 的復雜度為O(screen_resolution + num_lights)。
但是 Deferred Rendering 局限性也是顯而易見。比如我在 G-Buffer 存儲以下數據
這樣的話,對于一個普通的 1024x768 的屏幕分辨率。總共得使用 1024x768x128bit=20MB,對于目前的動則上 GB 的顯卡內存,可能不算什么。但是使用 G-Buffer 耗費的顯存還是很多的。一方面,對于低端顯卡,這么大的顯卡內存確實很耗費資源。另一方面,如果要渲染更酷的特效,使用的 G-Buffer 大小將增加,并且其增加的幅度也是很可觀的。順帶說一句,存取 G-Buffer 耗費的帶寬也是一個不可忽視的缺陷。
對于 Deferred Rendering 的優化也是一個很有挑戰的問題。下面簡單介紹幾種降低Deferred Rendering 存取帶寬的方式。最簡單也是最容易想到的就是將存取的 G-Buffer 數據結構最小化,這也就衍生除了 light pre-pass 方法。另一種方式是將多個光照組成一組,然后一起處理,這種方法衍生了 Tile-based deferred Rendering。
2.2.1 Light Pre-Pass
Light Pre-Pass 最早是由 Wolfgang Engel 在他的博客[2]中提到的。
具體的做法是:
(1)只在G-Buffer 中存儲 Z 值和 Normal 值。對比 Deferred Render,少了 Diffuse Color, Specular Color 以及對應位置的材質索引值。
(2)在 FS 階段利用上面的 G-Buffer 計算出所必須的 light properties,比如 Normal*LightDir,LightColor,Specular 等 light properties。將這些計算出的光照進行 alpha-blend 并存入 LightBuffer(就是用來存儲 light properties 的 buffer)。
(3)最后將結果送到 forward rendering 渲染方式計算最后的光照效果。
相對于傳統的 Deferred Render,使用 Light Pre-Pass 可以對每個不同的幾何體使用不同的 shader 進行渲染,所以每個物體的 material properties 將有更多變化。這里我們可以看出對于傳統的 Deferred Render,它的第二步(見偽代碼)是遍歷每個光源,這樣就增加了光源設置的靈活性,而 Light Pre-Pass 第三步使用的其實是 forward rendering,所以可以對每個mesh 設置其材質,這兩者是相輔相成的,有利有弊。另一個 Light Pre-Pass 的優點是在使用MSAA 上很有利。雖然并不是 100%使用上了 MSAA(除非使用 DX10/11 的特性),但是由于使用了 Z 值和 Normal 值,就可以很容易找到邊緣,并進行采樣。
下面這兩張圖,左邊是使用傳統 Deferred Render 繪制的,走遍是使用 Light Pre-Pass 繪制的。這兩張圖在效果上不應該有太大區別。
2.2.2 Tile-Based Deferred Rendering
TBDR 主要思想就是將屏幕分成一個個小塊 tile。然后根據這些 Depth 求得每個 tile 的bounding box。對每個 tile 的 bounding box 和 light 進行求交,這樣就得到了對該 tile 有作用的 light 的序列。最后根據得到的序列計算所在 tile 的光照效果。
對比 Deferred Render,之前是對每個光源求取其作用區域 light volume,然后決定其作用的的 pixel,也就是說每個光源要求取一次。而使用 TBDR,只要遍歷每個 pixel,讓其所屬tile 與光線求交,來計算作用其上的 light,并利用 G-Buffer 進行 Shading。一方面這樣做減少了所需考慮的光源個數,另一方面與傳統的 Deferred Rendering 相比,減少了存取的帶寬。
2.3 Forward+
Forward+ == Forward + Light Culling[6]。Forward+很類似 Tiled-based Deferred Rendering。其具體做法就是先對輸入的場景進行 z-prepass,也就是說關閉寫入 color,只向 z-buffer 寫入 z 值。注意此步驟是 Forward+必須的,而其他渲染方式是可選的。接下來來的步驟和 TBDR 很類似,都是劃分 tiles,并計算 bounding box。只不過 TBDR 是在 G-Buffer 中完成這一步驟的,而 Forward+是根據 Z-Buffer。最后一步其實使用的是 forward 方式,即在 FS 階段對每個pixel 根據其所在 tile 的 light 序列計算光照效果。而 TBDR 使用的是基于 G-Buffer 的 deferred rendering。
實際上,forward+比 deferred 運行的更快。我們可以看出由于 Forward+只要寫深度緩存就可以,而 Deferred Render 除了深度緩存,還要寫入法向緩存。而在 Light Culling 步驟,Forward+只需要計算出哪些 light 對該 tile 有影響即可。而 Deferred Render 還在這一部分把光照處理給做了。而這一部分,Forward+是放在 Shading 階段做的。所以 Shading階段 Forward+耗費更多時間。但是對目前硬件來說,Shading 耗費的時間沒有那么多。
Forward+的優勢還有很多,其實大多就是傳統 Forward Rendering 本身的優勢,所以Forward+更像一個集各種 Rendering Path 優勢于一體的 Rendering Path。
3. 總結
首先我們列出 Rendering Equation,然后對比 Forward Rendering,Deferred Rendering 和Forward+ Rendering。
3.1 Rendering Equation
其中點 x 處有一入射光,其光強為Le,入射角度為 wi。根據函數 f 和 Le來計算出射角為 wo)。注意此處的 n 為場景中總共有 n 個光源。
3.2 Forward Renderng
由于 Forward 本身對多光源支持力度不高,所以此處對于每個點 x 的處理不再考慮所有的 n 個光源,僅僅考慮少量的或者說經過挑選的 m 個光源。可以看出這樣的光照效果并不完美。另外,每個光線的V'(wo)是計算不了的。
3.3 Deferred Rendering
由于 Deferred Rendering 使用了 light culling,所以不用遍歷場景中的所有光源,只需遍歷經過 light culling 后的 n 個光源即可。并且 Deferred Rendering 將計算 BxDF 的部分單獨分出來了。
3.4 Forward+ Rendering
可以看出 Forward+和 Forward 最大區別就是光源的挑選上有了很到改進。
-
光源
+關注
關注
3文章
711瀏覽量
67897 -
光照
+關注
關注
0文章
53瀏覽量
11081
原文標題:實時渲染中常用的幾種 Rendering Path
文章出處:【微信號:Imgtec,微信公眾號:Imagination Tech】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
【RTC程序設計:實時音視頻權威指南】視頻采集與渲染
【RTC程序設計:實時音視頻權威指南】音頻采集與渲染
如何為GRID渲染設置適當的Xorg.conf ?
Unity中的局部立方體圖渲染技術解讀
實時云渲染在高校虛擬仿真教學中應用的優勢和價值分析
虛擬現實應用中的并行渲染技術
基于GPU的水面實時渲染算法
研究人員提出通過機器學習管道實現實時CG渲染
實時云渲染部署數字孿生顯卡要求
烘焙vs渲染:3D模型制作中的效率與質量之爭
![烘焙vs<b class='flag-5'>渲染</b>:3D模型制作<b class='flag-5'>中</b>的效率與質量之爭](https://file.elecfans.com/web2/M00/4E/DC/poYBAGLCjeiALm_WAAAYmfR7Qec474.png)
評論