1. 程式人生 > >【GPU精粹與Shader程式設計】(三) 《GPU Gems 1》全書核心內容提煉總結 · 下篇

【GPU精粹與Shader程式設計】(三) 《GPU Gems 1》全書核心內容提煉總結 · 下篇

本文由出品,首發於知乎專欄,轉載請註明出處 


題圖背景來自《神祕海域4》。

系列文章前言

《GPU Gems》1~3 、《GPU Pro》1~7 以及《GPU Zen》組成的饕餮盛宴,共11本書,合稱“GPU精粹三部曲“,是遊戲開發、計算機圖形學和渲染領域的業界頂尖大牛們一線經驗的合輯彙編,是江湖各大門派武林絕學經典招式的精華薈萃,是瞭解業界各種高階知識和技法Trick,將自己的遊戲開發、圖形學與渲染能力提升到下一個高度的捷徑。

本文內容概覽與關鍵詞

本篇文章將總結提煉“GPU精粹三部曲“11本書中的第一本《GPU Gems 1》全書的核心內容的下半部分,是【GPU精粹與Shader程式設計】系列文章正篇的第二篇,全文共1萬5千餘字。

本文內容的關鍵詞:

  • 真實感面板渲染(Realistic Skin Rendering)
  • 次表面散射(Subsurface Scattering)
  • 環境光遮蔽(Ambient Occlusion)
  • 實時輝光(Real-Time Glow)
  • 陰影的渲染(Shadow Rendering)
  • 透視陰影貼圖(Perspective Shadow Maps)
  • 逐畫素光照的可見性管理(Managing Visibility for Per-Pixel Lighting)
  • 空間BRDF(Spatial BRDFs)
  • 基於影象的光照(Image-Based Lighting,IBL)
  • 紋理爆炸(Texture Bombing)
  • 顏色控制(Color Controls)
  • 景深 (Depth of Field)
  • 高品質的影象濾波(High-Quality Filtering)
  • 快速濾波寬度的計算(Fast Filter-Width Estimates )
  • 高動態範圍(High-Dynamic-Range , HDR)

本文的GitHub版本

不少朋友們喜歡看GitHub版本的文章,我也很喜歡。

首先,MarkDown可以很方便地插入快捷導航目錄,能進行瞬間跳轉到指定子章節。其次,GitHub版本的文章中沒有單篇文章的字數限制,少了很多篇幅方面的桎梏。而且因為Git的便利性,版本管理的優勢,最新的勘誤和修訂第一時間會在GitHub的Repo中進行。

【本文的GitHub版本傳送門】:

目錄 · 核心內容導航Highlight

【說明】下文目錄中加粗的標題為本文將包括的內容,非加粗的標題已在上次的更新(GPU
Gems 1》全書核心內容提煉總結 · 上篇)中釋出。

另外需要注意,本文將原來在上篇中目錄為次核心內容的“二十一、實時輝光(Real-Time
Glow)”提升為了主核心內容,現為“六、實時輝光(Real-Time Glow)”。

本文將進行重點提煉總結的主核心內容有:

  • 一、用物理模型進行高效的水模擬(Effective Water Simulation from Physical
    Models)
  • 二、Dawn Demo中的面板渲染(Skin in the Dawn Demo)
  • 三、無盡波動的草地葉片的渲染(Rendering Countless Blades of Waving Grass)
  • 四、次表面散射的實時近似(Real-Time Approximations to Subsurface Scattering)
  • 五、環境光遮蔽(Ambient Occlusion)
  • 六、實時輝光(Real-Time Glow)

本文將進行提煉總結的次核心內容有:

  • 七、水焦散的渲染 (Rendering Water Caustics)
  • 八、 Dawn Demo中的動畫(Animation in the "Dawn" Demo)
  • 九、 改良的Perlin噪聲實現(Implementing Improved Perlin Noise)
  • 十、Vulcan Demo中的火焰渲染(Fire in the "Vulcan" Demo)
  • 十一、衍射的模擬(Simulating Diffraction)
  • 十二、高效的陰影體渲染(Efficient Shadow Volume Rendering)
  • 十三、電影級光照(Cinematic Lighting)
  • 十四、陰影貼圖抗鋸齒(Shadow Map Antialiasing)
  • 十五、全方位陰影對映(Omnidirectional Shadow Mapping)
  • 十六、使用遮擋區間對映產生模糊的陰影(Generating Soft Shadows Using Occlusion Interval Maps)
  • 十七、透視陰影貼圖(Perspective Shadow Maps: Care and Feeding)
  • 十八、逐畫素光照的可見性管理(Managing Visibility for Per-Pixel Lighting)
  • 十九、空間BRDF(Spatial BRDFs)
  • 二十、基於影象的光照(Image-Based Lighting)
  • 二十一、紋理爆炸(Texture Bombing)
  • 二十二、顏色控制(Color Controls)
  • 二十三、景深 (Depth of Field)
  • 二十四、高品質的影象濾波(High-Quality Filtering)
  • 二十五、用紋理貼圖進行快速濾波寬度的計算(Fast Filter-Width Estimates with Texture Maps)
  • 二十六、OpenEXR影象檔案格式與HDR(The OpenEXR Image File Format and HDR)

PS : 因為知乎專欄單篇文章字數限制在1萬5千字的原因,本文的“第二部分 · 次核心內容提煉總結”中所有小結的【本章配套原始碼彙總表】子欄目,以及參考文獻,將在下文中略去。需要檢視這些額外內容的同學,請移步至本文的GitHub版本:

《GPU Gems 1》配套資源與原始碼

這一節提供了一些,《GPU Gems 1》書本的相關連結,以及配套原始碼的下載。

PS:配套的不少工程中不僅包含完整的原始碼,也直接包含經過編譯後的exe執行檔案,可以直接執行後檢視效果。

  • 原書全文的Web版本:
  • 我維護了的一個名為“GPU-Gems-CD-Content”的GitHub倉庫,並整理好了《GPU Gems》1~3 書本全部的配套工程與原始碼,逐章內容的快捷導航,以備份、珍貴、快速查閱這些優質資源:

第一部分 · 主核心內容提煉總結

四、次表面散射的實時近似(Real-Time Approximations to Subsurface Scattering)

【章節概覽】

次表面散射(Subsurface Scattering),簡稱SSS,或3S,是光射入非金屬材質後在內部發生散射,最後射出物體並進入視野中產生的現象,即光從表面進入物體經過內部散射,然後又通過物體表面的其他頂點出射的光線傳遞過程。


                                                                           圖 次表面散射原理圖示

圖 真實世界中的次表面散射

要產生使人信服的面板和其他半透明材質的渲染效果,次表面散射(Subsurface Scattering)的渲染效果十分重要。

圖 有無次表面散射的面板渲染對比圖(左圖:使用次表面散射 | 右圖:無次表面散射)

另外需要提出,在《神祕海域4》中面板的渲染效果,很令人驚豔。當然,《神祕海域4》中令人驚豔的,遠遠不止面板的渲染。

圖 基於次表面散射的面板渲染 @《神祕海域4》

本章即描述了次表面散射的幾種實時近似方法,關於面板的渲染,也關於近似地去模擬透明材質的幾種不同方法。

【核心內容提煉】

4.1 次表面散射的視覺特性(The Visual Effects of Subsurface Scattering)

要重現出任何視覺效果,經常的做法是考察這種效果的影象,並把可視的外觀分解為其組成要素。在觀察半透明物體的相片和影象時,能注意到如下幾點,即次表面散射(Subsurface
Scattering)的視覺特性:

1、首先,次表面散射往往使照明的整體效果變得柔和。

2、一個區域的光線往往滲透到表面的周圍區域,而小的表面細節變得看不清了。

3、光線傳入物體越深,就衰減和散射得越嚴重。

4、對於面板來說,在照亮區到陰影區的銜接處,散射往往會引起微弱的傾向於紅色的顏色偏移。這是由於光線照亮表皮並進入面板,接著被皮下血管和組織散射和吸收,然後從陰影部分離開。且散射在面板薄的部位更加明顯,比如鼻孔和耳朵周圍。


                                                                       圖 次表面散射原理圖示

4.2 簡單的散射近似(Simple Scattering Approximations)

近似散射的比較簡單技巧是環繞照明(Warp Lighting)。正常情況下,當表面的法線對於光源方向垂直的時候,Lambert漫反射提供的照明度是0。而環繞光照修改漫反射函式,使得光照環繞在物體的周圍,越過那些正常時會變黑變暗的點。這減少了漫反射光照明的對比度,從而減少了環境光和所要求的填充光的量。環繞光照是對Oren-Nayar光照模型的一個粗糙的近似。原模型力圖更精確地模擬粗糙的不光滑表面(Nayar and Oren 1995)。

下圖和程式碼片段顯示瞭如何將漫反射光照函式進行改造,使其包含環繞效果。

其中,wrap變數為環繞值,是一個範圍為0到1之間的浮點數,用於控制光照環繞物體周圍距離。

圖 環繞光照函式的圖表

float diffuse = max(0, dot(L, N));

float wrap_diffuse = max(0, (dot(L, N) + wrap) / (1 + wrap));

為了在片元函式程式中的計算可以更加高效,上述函式可以直接編碼到紋理中,用光線向量和法線的點積為索引。

而在照明度接近0時,可以顯示出那種傾向於紅的微小顏色漂移,這是模擬面板散射的一種廉價方法。而這種偏向於紅色的微小顏色漂移,也可以直接加入到此紋理中。

另外也可以在此紋理的alpha通道中加入鏡面反射高光光照的功率(power)函式。可以在示例程式碼Example 16-1中的FX程式碼展示瞭如何使用這種技術。對比的圖示如下。

                          圖 (a)沒有環繞光照的球體 (b)有環繞光照明的球體 (c)有環繞光照明和顏色漂移的球體

Example 16-1 摘錄納入了環繞照明的面板Shader效果的程式碼(Excerpt from the Skin
Shader Effect Incorporating Wrap Lighting)

// 為面板著色生成2D查詢表(Generate 2D lookup table for skin shading)

float4 GenerateSkinLUT(float2 P : POSITION) : COLOR

{

    float wrap = 0.2;

    float scatterWidth = 0.3;

    float4 scatterColor = float4(0.15, 0.0, 0.0, 1.0);

    float shininess = 40.0;

    float NdotL = P.x * 2 - 1; // remap from [0, 1] to [-1, 1]

    float NdotH = P.y * 2 - 1;

    float NdotL_wrap = (NdotL + wrap) / (1 + wrap); // wrap lighting

    float diffuse = max(NdotL_wrap, 0.0);

    // 在從明到暗的轉換中新增顏色色調(add color tint at transition from light to
    dark)

    float scatter = smoothstep(0.0, scatterWidth, NdotL_wrap) *

    smoothstep(scatterWidth * 2.0, scatterWidth,

         NdotL_wrap);

    float specular = pow(NdotH, shininess);

    if (NdotL_wrap <= 0) specular = 0;

    float4 C;

    C.rgb = diffuse + scatter * scatterColor;

    C.a = specular;

    return C;

}

// 使用查詢表著色面板(Shade skin using lookup table)

half3 ShadeSkin(sampler2D skinLUT,

    half3 N,

    half3 L,

    half3 H,

    half3 diffuseColor,

    half3 specularColor) : COLOR

{

    half2 s;

    s.x = dot(N, L);

    s.y = dot(N, H);

    half4 light = tex2D(skinLUT, s * 0.5 + 0.5);

    return diffuseColor * light.rgb + specularColor * light.a;

}

4.3 使用深度貼圖模擬吸收(Simulating Absorption Using Depth Maps)

吸收(Absorption)是模擬半透明材質的最重要特性之一。光線在物質中傳播得越遠,它被散射和吸收得就越厲害。為了模擬這種效果,我們需要測量光在物質中傳播的距離。而估算這個距離可以使用深度貼圖(Depth Maps)技術[Hery 2002],此技術非常類似於陰影貼圖(Shadow Mapping),而且可用於實時渲染。

                                                                  圖 使用深度貼圖計算光在物體中的傳播的距離

深度貼圖(Depth Maps)技術的思路是:

在第一個通道(first pass)中,我們從光源的視點處渲染場景,儲存從光源到某個紋理的距離。然後使用標準的投射紋理貼圖(standard projective texture mapping),將該影象投射回場景。在渲染通道(rendering pass)中,給定一個需要著色的點,我們可以查詢這個紋理,來獲得從光線進入表面的點(di)到光源間距離,通過從光線到光線離開表面的距離(do)裡減去這個值,我們便可以獲得光線轉過物體內部距離長度的一個估計值(S)。如上圖。

原文中詳細分析了此方法的實現過程,也附帶了完整的Shader原始碼,具體細節可以檢視原文,這裡因為篇幅原因就不展開了。

                                                圖 使用深度貼圖去近似散射,物體上薄的部位傳輸更多的光

也有一些更高階的模型試圖更精確地模擬介質內散射的累積效應。

一種模型是單次散射近似(Single Scattering Approximation),其假設光在材質中只反彈一次,沿著材質內的折射光線,可以計算有多少光子會朝向攝像機散射。當光擊中一個粒子的時候,光散射方向的分佈用相位函式來描述。而考慮入射點和出射點的菲涅爾效應也很重要。

另一種模型,是近似漫反射(Diffusion Approximation),其用來模擬高散射介質(如面板)的多次散射效果。

4.4 紋理空間的漫反射(Texture-Space Diffusion)

次表面散射最明顯的視覺特徵之一是模糊的光照效果。其實,3D美術時常在螢幕空間中效仿這個現象,通過在Photoshop中執行Gaussian模糊,然後把模糊影象少量地覆蓋在原始影象上,這種“輝光”技術使光照變得柔和。

而在紋理空間中模擬漫反射[Borshukov and Lewis 2003],即紋理空間漫反射(Texture-Space Diffusion)是可能的,我們可以用頂點程式展開物體的網格,程式使用紋理座標UV作為頂點的螢幕位置。程式簡單地把[0,1]範圍的紋理座標重對映為[-1,1]的規範化的座標。

另外,為了模擬吸收和散射與波長的相關的事實,可以對每個彩色通道分為地改變濾波權重。

                                       圖 (a)原始模型 (b)應用了紋理空間漫反射照明的模型,光照變得柔和
                                                                        圖 基於紋理空間漫反射照明的效果

同樣,原文中詳細分析了此方法的實現過程,也附帶了完整的Shader原始碼,具體細節可以檢視原文,這裡因為篇幅原因就不展開了。

再貼幾張基於次表面散射的面板渲染效果圖,結束這一節。


                                                                        圖 基於次表面散射的面板渲染


                                                          圖 基於次表面散射的面板渲染 @《神祕海域4》


                                                                圖 基於次表面散射的面板渲染 @《神祕海域4》

【核心要點總結】

文中提出的次表面散射的實時近似方法,總結起來有三個要點:

1) 基於環繞照明(Warp Lighting)的簡單散射近似,Oren-Nayar光照模型。

2) 使用深度貼圖來模擬半透明材質的最重要特性之一——吸收(Absorption)。

3)基於紋理空間中的漫反射模擬(Texture-Space Diffusion),來模擬次表面散射最明顯的視覺特徵之一——模糊的光照效果。

【本章配套原始碼彙總表】

Example 16-1 摘錄納入了環繞照明的面板Shader效果的程式碼(Excerpt from the Skin Shader Effect Incorporating Wrap Lighting)

Example 16-2 深度Pass的頂點Shader程式碼(The Vertex Program for the Depth Pass)

Example 16-3 深度Pass的片元Shader程式碼(The Fragment Program for the Depth Pass)

Example 16-4 使用深度貼圖來計算穿透深度的片元Shader程式碼(The Fragment Program Function for Calculating Penetration Depth Using Depth Map)

Example 16-5 用於展開模型和執行漫反射光照的頂點Shader程式碼(A Vertex Program to Unwrap a Model and Perform Diffuse Lighting)

Example 16-6 用於漫反射模糊的頂點Shader程式碼(The Vertex Program for Diffusion Blur)

Example 16-7 用於漫反射模糊的片元Shader程式碼(The Fragment Program for Diffusion Blur)

【關鍵詞提煉】

面板渲染(Skin Rendering)

次表面散射(Subsurface Scattering)

紋理空間漫反射(Texture-Space Diffusion)

環繞照明(Warp Lighting)

深度對映(Depth Maps)

五、環境光遮蔽(Ambient Occlusion)

【章節概覽】

在某種意義上,這篇文章屬於環境光遮蔽的啟蒙式文章。

環境光遮蔽(Ambient Occlusion),簡稱AO,是一種用於計算場景中每個點對環境光照的曝光程度的一種著色渲染技術。

本章講到了如何使用有效的實時環境光遮蔽技術,對物體遮蔽資訊及環境進行預處理,綜合這些因素給物體建立逼真的光照和陰影。

                                                                          圖 有無環境光遮蔽的對比                                                                          圖 有無環境光遮蔽的對比

【核心內容提煉】

5.1 概述

首先,本文中講到,環境光遮蔽(Ambient Occlusion)一般而言有兩種理解:

1)將環境光遮蔽視為“智慧”的環境光項,其在模型表面的變化取決於在每點可見多少外部環境。

2)將環境光遮蔽視為一個漫反射項,其能有效地支援複雜分佈的入射光線。

文中將考慮上述的第二種解釋。

其基本思路是,假如預處理一個模型,計算它上面每個點可以看到多少外部環境,可以相反地計算有多少環境被模型的其他部分遮擋,然後在渲染時使用這個資訊計算漫反射著色項的值。其結果是模型上的裂縫變暗,而模型的暴露部分會接收更多的光線,因此更明亮。這種效果實質上比使用標準的著色模型更逼真。

另外,這個方法可以擴充套件為使用環境光作為照明源,用代表各個方向入射光的環境貼圖沒來決定物體上每個點光的顏色。為了這個特性,除了記錄在點上可以看到多少外部環境之外,也記錄大部分可以光從哪個方向到達。這兩個量有效地定義了從外面進入場景的未被遮擋的方向圓錐體,可以一起用來做為來自環境貼圖的極端模糊的查詢,模擬著色點上來自感興趣的方向圓錐體的全部入射照度。

5.2 預處理步驟(The Preprocessing Step)

給定一個任意的著色模型,環境光遮蔽演算法需要知道模型上每點的兩個資訊:

(1)該點的“可到達度(accessibility)”- 即該點上方半球的哪一部分未被模型的其他部分遮擋;

(2)未被遮擋的入射光的平均方向。

通過下圖在平面上說明這兩個概念。給定在表面上的點P,其法線為N,P點上半球的2/3被場景中其他幾何體遮擋,半球另外的1/3不被遮擋。入射光的平均方向用B表示,其在法線N的右側。大致來說,在P點的入射光的平均顏色,可以通過求圍繞B向量的未遮擋入射光的圓錐體的平均值得到。

                                                                          圖 可到達度和平均方向的計算

下面貼出的虛擬碼顯示了我們的基本方法。在每個三角形的中心,我們產生一組以表面法線為中心的半球形光線,跟蹤每道光線進入場景,記錄哪些光線與模型相交,標誌不能從環境接收的光線,以及不被遮擋的光線。接著我們計算不被遮擋的光線的平均方向,這給出了入射光平均方向的近似值。(當然,我們計算的方向實際上可能會被遮擋,但我們選擇忽略不計這個問題。)

Example 17-1 計算環境光遮蔽量的基本演算法虛擬碼 (Basic Algorithm for Computing Ambient Occlusion Quantities)

For each triangle {

    Compute center of triangle

    Generate set of rays over the hemisphere there

    Vector avgUnoccluded = Vector(0, 0, 0);

    int numUnoccluded = 0;

    For each ray {

        If (ray doesn't intersect anything) {

            avgUnoccluded += ray.direction;

            ++numUnoccluded;

        }

    }

    avgUnoccluded = normalize(avgUnoccluded);

    accessibility = numUnoccluded / numRays;

}

生成這些光線的簡單方法是使用拒絕取樣法(rejection sampling):檢測在x,y和z為-1到1區間的3D立方體中隨機生成的光線,並拒絕不在單位半球中與法線相關的光線。

能通過這次檢測的光線方向可視分佈理想的光線方向。列表17-2的偽程式碼表示出了此方法的實現思路。

當然,也可以用更復雜的蒙特卡洛(Monte Carlo)取樣法來得到更好的樣本方向的分佈。

Example 17-2 使用拒絕取樣法計算隨機方向的演算法虛擬碼(Algorithm for Computing
Random Directions with Rejection Sampling)

while (true) {

    x = RandomFloat(-1, 1); // random float between -1 and 1

    y = RandomFloat(-1, 1);

    z = RandomFloat(-1, 1);

    if (x * x + y * y + z * z > 1) continue; // ignore ones outside unit

    // sphere

    if (dot(Vector(x, y, z), N) < 0) continue; // ignore "down" dirs

    return normalize(Vector(x, y, z)); // success!

}

另外,用圖形硬體代替光線追蹤軟體,有可能加速遮擋資訊的計算。

5.3 使用環境光遮蔽貼圖進行渲染(Rendering with Ambient Occlusion Maps)

使用環境光遮蔽貼圖進行著色的基本思想是:

可以直接在著色點處使用之前已計算好的,有多少光線能到達表面的,優質的近似值資訊。

影響這個數值的兩個因素是:

(1)在此點上方半球的哪個部分不被點和環境貼圖之間的幾何體遮擋。

(2)沿著這些方向的入射光是什麼。

下圖顯示了兩種不同的情況。在左圖中,只能看到著色點上面的一小部分分享,由方向向量B和圍繞它的方向圓錐體所表示,該點的可到達度非常低。而在右圖中,沿著更大範圍的方向有更多的光線到達給定點。


圖 不同量的可見度的近似(左圖:由於附近的幾何體的遮擋比較嚴重,這點得到的照度較小;右圖:沿著更寬方向的圓錐體,更大量的光能到達這點,照度較左圖更大)

在預處理中計算的可訪問性值告訴我們哪一部分半球可以看到環境貼圖,而可見方向的平均值給出一個近似方向,圍繞它計算入射光。雖然這個方向可能指向一個實際被遮擋的方向(例如,如果半球的兩個獨立區域未被遮擋,但其餘的部分被遮擋,平均方向可能在這兩者之間),但在實踐中其通常執行良好。