1. 程式人生 > >opengl學習之路三十七,理論

opengl學習之路三十七,理論

Note

本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。

PBR,或者用更通俗一些的稱呼是指基於物理的渲染(Physically Based Rendering),它指的是一些在不同程度上都基於與現實世界的物理原理更相符的基本理論所構成的渲染技術的集合。正因為基於物理的渲染目的便是為了使用一種更符合物理學規律的方式來模擬光線,因此這種渲染方式與我們原來的Phong或者Blinn-Phong光照演算法相比總體上看起來要更真實一些。除了看起來更好些以外,由於它與物理性質非常接近,因此我們(尤其是美術師們)可以直接以物理引數為依據來編寫表面材質,而不必依靠粗劣的修改與調整來讓光照效果看上去正常。使用基於物理引數的方法來編寫材質還有一個更大的好處,就是不論光照條件如何,這些材質看上去都會是正確的,而在非PBR的渲染管線當中有些東西就不會那麼真實了。

雖然如此,基於物理的渲染仍然只是對基於物理原理的現實世界的一種近似,這也就是為什麼它被稱為基於物理的著色(Physically based Shading) 而非物理著色(Physical Shading)的原因。判斷一種PBR光照模型是否是基於物理的,必須滿足以下三個條件(不用擔心,我們很快就會了解它們的): 1.基於微平面(Microfacet)的表面模型。 2.能量守恆。 3.應用基於物理的BRDF。

在這次的PBR系列教程之中,我們將會把重點放在最先由迪士尼(Disney)提出探討並被Epic Games首先應用於實時渲染的PBR方案。他們基於金屬質地工作流(Metallic Workflow)的方案有非常完備的文獻記錄,廣泛應用於各種流行的引擎之中並且有著非常令人驚歎的視覺效果。完成這次的教程之後我們將會製作出類似於這樣的一些東西:

在這裡插入圖片描述

注意這次的理論教程是一個三部曲系列(目前還在創作中)的一部分,所以這部分教程的內容有可能隨著其他兩部還沒完成的教程的進展而發生變化。同樣,這部教程的原始碼也並不完整,所以上面的圖片是取自一個私人專案之中,而非本教程的程式碼所生成的。不過在完成之後這個圖片看上去應該會好很多。

請注意這個系列的教程所探討的內容屬於相當高階的領域,因此要求讀者對OpenGL和著色器光照有較好的理解。讀者將會需要這些相關的知識:幀緩衝,立方體貼圖,Gamma校正,HDR和法線貼圖。我們還會深入探討一些高等數學的內容,我會盡我所能將相關的概念闡述清楚。

微平面模型

所有的PBR技術都基於微平面理論。這項理論認為,達到微觀尺度之後任何平面都可以用被稱為微平面(Microfacets)的細小鏡面來進行描繪。根據平面粗糙程度的不同,這些細小鏡面的取向排列可以相當不一致: 在這裡插入圖片描述

產生的效果就是:一個平面越是粗糙,這個平面上的微平面的排列就越混亂。這些微小鏡面這樣無序取向排列的影響就是,當我們特指鏡面光/鏡面反射時,入射光線更趨向於向完全不同的方向發散(Scatter)開來,進而產生出分佈範圍更廣泛的鏡面反射。而與之相反的是,對於一個光滑的平面,光線大體上會更趨向於向同一個方向反射,造成更小更銳利的反射:

在這裡插入圖片描述

在微觀尺度下,沒有任何平面是完全光滑的。然而由於這些微平面已經微小到無法逐畫素的繼續對其進行區分,因此我們只有假設一個粗糙度(Roughness)引數,然後用統計學的方法來概略的估算微平面的粗糙程度。我們可以基於一個平面的粗糙度來計算出某個向量的方向與微平面平均取向方向一致的概率。這個向量便是位於光線向量l l 和視線向量v v 之間的中間向量(Halfway Vector)。我們曾經在之前的高階光照教程中談到過中間向量,它的計算方法如下:

在這裡插入圖片描述

微平面的取向方向與中間向量的方向越是一致,鏡面反射的效果就越是強烈越是銳利。然後再加上一個介於0到1之間的粗糙度引數,這樣我們就能概略的估算微平面的取向情況了: 在這裡插入圖片描述

我們可以看到,較高的粗糙度值顯示出來的鏡面反射的輪廓要更大一些。與之相反地,較小的粗糙值顯示出的鏡面反射輪廓則更小更銳利。

能量守恆

微平面近似法使用了這樣一種形式的能量守恆(Energy Conservation):出射光線的能量永遠不能超過入射光線的能量(發光面除外)。如圖示我們可以看到,隨著粗糙度的上升鏡面反射區域的會增加,但是鏡面反射的亮度卻會下降。如果不管反射輪廓的大小而讓每個畫素的鏡面反射強度(Specular Intensity)都一樣的話,那麼粗糙的平面就會放射出過多的能量,而這樣就違背了能量守恆定律。這也就是為什麼正如我們看到的一樣,光滑平面的鏡面反射更強烈而粗糙平面的反射更昏暗。

為了遵守能量守恆定律,我們需要對漫反射光和鏡面反射光之間做出明確的區分。當一束光線碰撞到一個表面的時候,它就會分離成一個折射部分和一個反射部分。反射部分就是會直接反射開來而不會進入平面的那部分光線,這就是我們所說的鏡面光照。而折射部分就是餘下的會進入表面並被吸收的那部分光線,這也就是我們所說的漫反射光照。

這裡還有一些細節需要處理,因為當光線接觸到一個表面的時候折射光是不會立即就被吸收的。通過物理學我們可以得知,光線實際上可以被認為是一束沒有耗盡就不停向前運動的能量,而光束是通過碰撞的方式來消耗能量。每一種材料都是由無數微小的粒子所組成,這些粒子都能如下圖所示一樣與光線發生碰撞。這些粒子在每次的碰撞中都可以吸收光線所攜帶的一部分或者是全部的能量而後轉變成為熱量。

在這裡插入圖片描述

一般來說,並非所有能量都會被全部吸收,而光線也會繼續沿著(基本上)隨機的方向發散,然後再和其他的粒子碰撞直至能量完全耗盡或者再次離開這個表面。而光線脫離物體表面後將會協同構成該表面的(漫反射)顏色。不過在基於物理的渲染之中我們進行了簡化,假設對平面上的每一點所有的折射光都會被完全吸收而不會散開。而有一些被稱為次表面散射(Subsurface Scattering)技術的著色器技術將這個問題考慮了進去,它們顯著的提升了一些諸如面板,大理石或者蠟質這樣材質的視覺效果,不過伴隨而來的則是效能下降代價。

對於金屬(Metallic)表面,當討論到反射與折射的時候還有一個細節需要注意。金屬表面對光的反應與非金屬材料還有電介質(Dielectrics)材料表面相比是不同的。它們遵從的反射與折射原理是相同的,但是所有的折射光都會被直接吸收而不會散開,只留下反射光或者說鏡面反射光。亦即是說,金屬表面不會顯示出漫反射顏色。由於金屬與電介質之間存在這樣明顯的區別,因此它們兩者在PBR渲染管線中被區別處理,而我們將在文章的後面進一步詳細探討這個問題。

反射光與折射光之間的這個區別使我們得到了另一條關於能量守恆的經驗結論:反射光與折射光它們二者之間是互斥的關係。無論何種光線,其被材質表面所反射的能量將無法再被材質吸收。因此,諸如折射光這樣的餘下的進入表面之中的能量正好就是我們計算完反射之後餘下的能量。

我們按照能量守恆的關係,首先計算鏡面反射部分,它的值等於入射光線被反射的能量所佔的百分比。然後折射光部分就可以直接由鏡面反射部分計算得出: float kS = calculateSpecularComponent(…); // 反射/鏡面 部分 float kD = 1.0 - ks; // 折射/漫反射 部分

這樣我們就能在遵守能量守恆定律的前提下知道入射光線的反射部分與折射部分所佔的總量了。按照這種方法折射/漫反射與反射/鏡面反射所佔的份額都不會超過1.0,如此就能保證它們的能量總和永遠不會超過入射光線的能量。而這些都是我們在前面的光照教程中沒有考慮的問題。

反射率方程

在這裡我們引入了一種被稱為渲染方程(Render Equation)的東西。它是某些聰明絕頂人所構想出來的一個精妙的方程式,是如今我們所擁有的用來模擬光的視覺效果最好的模型。基於物理的渲染所堅定的遵循的是一種被稱為反射率方程(The Reflectance Equation)的渲染方程的特化版本。要正確的理解PBR 很重要的一點就是要首先透徹的理解反射率方程:

在這裡插入圖片描述

反射率方程一開始可能會顯得有些嚇人,不過隨著我們慢慢對其進行剖析,讀者最終會逐漸理解它的。要正確的理解這個方程式,我們必須要稍微涉足一些輻射度量學(Radiometry)的內容。輻射度量學是一種用來度量電磁場輻射(包括可見光)的手段。有很多種輻射度量(radiometric quantities)可以用來測量曲面或者某個方向上的光,但是我們將只會討論其中和反射率方程有關的一種。它被稱為輻射率(Radiance),在這裡用L L 來表示。輻射率被用來量化單一方向上發射來的光線的大小或者強度。由於輻射率是由許多物理變數集合而成的,一開始理解起來可能有些困難,因此我們首先關注一下這些物理量:

輻射通量:輻射通量Φ Φ 表示的是一個光源所輸出的能量,以瓦特為單位。光是由多種不同波長的能量所集合而成的,而每種波長則與一種特定的(可見的)顏色相關。因此一個光源所放射出來的能量可以被視作這個光源包含的所有各種波長的一個函式。波長介於390nm到700nm(納米)的光被認為是處於可見光光譜中,也就是說它們是人眼可見的波長。在下面你可以看到一幅圖片,裡面展示了日光中不同波長的光所具有的能量: 在這裡插入圖片描述

輻射通量將會計算這個由不同波長構成的函式的總面積。直接將這種對不同波長的計量作為引數輸入計算機圖形有一些不切實際,因此我們通常不直接使用波長的強度而是使用三原色編碼,也就是RGB(或者按通常的稱呼:光色)來作為輻射通量表示的簡化。這套編碼確實會帶來一些資訊上的損失,但是這對於視覺效果上的影響基本可以忽略。

立體角:立體角用ω ω 表示,它可以為我們描述投射到單位球體上的一個截面的大小或者面積。投射到這個單位球體上的截面的面積就被稱為立體角(Solid Angle),你可以把立體角想象成為一個帶有體積的方向:

在這裡插入圖片描述

可以把自己想象成為一個站在單位球面的中心的觀察者,向著投影的方向看。這個投影輪廓的大小就是立體角。

輻射強度:輻射強度(Radiant Intensity)表示的是在單位球面上,一個光源向每單位立體角所投送的輻射通量。舉例來說,假設一個全向光源向所有方向均勻的輻射能量,輻射強度就能幫我們計算出它在一個單位面積(立體角)內的能量大小: 在這裡插入圖片描述

計算輻射強度的公式如下所示:

在這裡插入圖片描述

其中I I 表示輻射通量Φ Φ 除以立體角ω ω 。

在理解了輻射通量,輻射強度與立體角的概念之後,我們終於可以開始討論輻射率的方程式了。這個方程表示的是,一個擁有輻射強度Φ Φ 的光源在單位面積A A ,單位立體角ω ω 上的輻射出的總能量:

在這裡插入圖片描述

輻射率是輻射度量學上表示一個區域平面上光線總量的物理量,它受到入射(Incident)(或者來射)光線與平面法線間的夾角θ θ 的餘弦值cosθ cos⁡θ 的影響:當直接輻射到平面上的程度越低時,光線就越弱,而當光線完全垂直於平面時強度最高。這和我們在前面的基礎光照教程中對於漫反射光照的概念相似,其中cosθ cos⁡θ 就直接對應於光線的方向向量和平面法向量的點積: float cosTheta = dot(lightDir, N);

輻射率方程很有用,因為它把大部分我們感興趣的物理量都包含了進去。如果我們把立體角ω ω 和麵積A A 看作是無窮小的,那麼我們就能用輻射率來表示單束光線穿過空間中的一個點的通量。這就使我們可以計算得出作用於單個(片段)點上的單束光線的輻射率,我們實際上把立體角ω ω 轉變為方向向量ω ω 然後把面A A 轉換為點p p 。這樣我們就能直接在我們的著色器中使用輻射率來計算單束光線對每個片段的作用了。

事實上,當涉及到輻射率時,我們通常關心的是所有投射到點p p 上的光線的總和,而這個和就稱為輻射照度或者輻照度(Irradiance)。在理解了輻射率和輻照度的概念之後,讓我們再回過頭來看看反射率方程:

在這裡插入圖片描述

我們知道在渲染方程中L L 代表通過某個無限小的立體角ω i ωi 在某個點上的輻射率,而立體角可以視作是入射方向向量ω i ωi 。注意我們利用光線和平面間的入射角的餘弦值cosθ cos⁡θ 來計算能量,亦即從輻射率公式L L 轉化至反射率公式時的n⋅ω i n⋅ωi 。用ω o ωo 表示觀察方向,也就是出射方向,反射率公式計算了點p p 在ω o ωo 方向上被反射出來的輻射率L o (p,ω o ) Lo(p,ωo) 的總和。或者換句話說:L o Lo 表示了從ω o ωo 方向上觀察,光線投射到點p p 上反射出來的輻照度。

基於反射率公式是圍繞所有入射輻射率的總和,也就是輻照度來計算的,所以我們需要計算的就不只是是單一的一個方向上的入射光,而是一個以點p p 為球心的半球領域Ω Ω 內所有方向上的入射光。一個半球領域(Hemisphere)可以描述為以平面法線n n 為軸所環繞的半個球體:

在這裡插入圖片描述

為了計算某些面積的值,或者像是在半球領域的問題中計算某一個體積的時候我們會需要用到一種稱為積分(Integral)的數學手段,也就是反射率公式中的符號∫ ∫ ,它的運算包含了半球領域Ω Ω 內所有入射方向上的dω i dωi 。積分運算的值等於一個函式曲線的面積,它的計算結果要麼是解析解要麼就是數值解。由於渲染方程和反射率方程都沒有解析解,我們將會用離散的方法來求得這個積分的數值解。這個問題就轉化為,在半球領域Ω Ω 中按一定的步長將反射率方程分散求解,然後再按照步長大小將所得到的結果平均化。這種方法被稱為黎曼和(Riemann sum) ,我們可以用下面的程式碼粗略的演示一下: int steps = 100; float sum = 0.0f; vec3 P = …; vec3 Wo = …; vec3 N = …; float dW = 1.0f / steps; for(int i = 0; i < steps; ++i) { vec3 Wi = getNextIncomingLightDir(i); sum += Fr(p, Wi, Wo) * L(p, Wi) * dot(N, Wi) * dW; }

通過利用dW來對所有離散部分進行縮放,其和最後就等於積分函式的總面積或者總體積。這個用來對每個離散步長進行縮放的dW可以認為就是反射率方程中的dω i dωi 。在數學上,用來計算積分的dω i dωi 表示的是一個連續的符號,而我們使用的dW在程式碼中和它並沒有直接的聯絡(因為它代表的是黎曼和中的離散步長),這樣說是為了可以幫助你理解。請牢記,使用離散步長得到的是函式總面積的一個近似值。細心的讀者可能已經注意到了,我們可以通過增加離散部分的數量來提高黎曼和的準確度(Accuracy)。

反射率方程概括了在半球領域Ω Ω 內,碰撞到了點p p 上的所有入射方向ω i ωi 上的光線的輻射率,並受到f r fr 的約束,然後返回觀察方向上反射光的L o Lo 。正如我們所熟悉的那樣,入射光輻射率可以由光源處獲得,此外還可以利用一個環境貼圖來測算所有入射方向上的輻射率,我們將在未來的IBL教程中討論這個方法。

現在唯一剩下的未知符號就是f r fr 了,它被稱為BRDF,或者雙向反射分佈函式(Bidirectional Reflective Distribution Function) ,它的作用是基於表面材質屬性來對入射輻射率進行縮放或者加權。

BRDF

BRDF,或者說雙向反射分佈函式,它接受入射(光)方向ω i ωi ,出射(觀察)方向ω o ωo ,平面法線n n 以及一個用來表示微平面粗糙程度的引數a a 作為函式的輸入引數。BRDF可以近似的求出每束光線對一個給定了材質屬性的平面上最終反射出來的光線所作出的貢獻程度。舉例來說,如果一個平面擁有完全光滑的表面(比如鏡面),那麼對於所有的入射光線ω i ωi (除了一束以外)而言BRDF函式都會返回0.0 ,只有一束與出射光線ω o ωo 擁有相同(被反射)角度的光線會得到1.0這個返回值。

BRDF基於我們之前所探討過的微平面理論來近似的求得材質的反射與折射屬性。對於一個BRDF,為了實現物理學上的可信度,它必須遵守能量守恆定律,也就是說反射光線的總和永遠不能超過入射光線的總量。嚴格上來說,同樣採用ω i ωi 和ω o ωo 作為輸入引數的 Blinn-Phong光照模型也被認為是一個BRDF。然而由於Blinn-Phong模型並沒有遵循能量守恆定律,因此它不被認為是基於物理的渲染。現在已經有很好幾種BRDF都能近似的得出物體表面對於光的反應,但是幾乎所有實時渲染管線使用的都是一種被稱為Cook-Torrance BRDF模型。

Cook-Torrance BRDF兼有漫反射和鏡面反射兩個部分:

在這裡插入圖片描述

這裡的k d kd 是早先提到過的入射光線中被折射部分的能量所佔的比率,而k s ks 是被反射部分的比率。BRDF的左側表示的是漫反射部分,這裡用f lambert flambert 來表示。它被稱為Lambertian漫反射,這和我們之前在漫反射著色中使用的常數因子類似,用如下的公式來表示:

在這裡插入圖片描述

c c 表示表面顏色(回想一下漫反射表面紋理)。除以π π 是為了對漫反射光進行標準化,因為前面含有BRDF的積分方程是受π π 影響的(我們會在IBL的教程中探討這個問題的)。

你也許會感到好奇,這個Lambertian漫反射和我們之前經常使用的漫反射到底有什麼關係:之前我們是用表面法向量與光照方向向量進行點乘,然後再將結果與平面顏色相乘得到漫反射引數。點乘依然還在,但是卻不在BRDF之內,而是轉變成為了L o Lo 積分末公式末尾處的n⋅ω i n⋅ωi 。

目前存在著許多不同型別的模型來實現BRDF的漫反射部分,大多看上去都相當真實,但是相應的運算開銷也非常的昂貴。不過按照Epic公司給出的結論,Lambertian漫反射模型已經足夠應付大多數實時渲染的用途了。

BRDF的鏡面反射部分要稍微更高階一些,它的形式如下所示: 在這裡插入圖片描述

Cook-Torrance BRDF的鏡面反射部分包含三個函式,此外分母部分還有一個標準化因子 。字母D,F與G分別代表著一種型別的函式,各個函式分別用來近似的計算出表面反射特性的一個特定部分。三個函式分別為正態分佈函式(Normal Distribution Function),菲涅爾方程(Fresnel Rquation)和幾何函式(Geometry Function): •正態分佈函式:估算在受到表面粗糙度的影響下,取向方向與中間向量一致的微平面的數量。這是用來估算微平面的主要函式。 •幾何函式:描述了微平面自成陰影的屬性。當一個平面相對比較粗糙的時候,平面表面上的微平面有可能擋住其他的微平面從而減少表面所反射的光線。 •菲涅爾方程:菲涅爾方程描述的是在不同的表面角下表面所反射的光線所佔的比率。

以上的每一種函式都是用來估算相應的物理引數的,而且你會發現用來實現相應物理機制的每種函式都有不止一種形式。它們有的非常真實,有的則效能高效。你可以按照自己的需求任意選擇自己想要的函式的實現方法。英佩遊戲公司的Brian Karis對於這些函式的多種近似實現方式進行了大量的研究。我們將會採用Epic Games在Unreal Engine 4中所使用的函式,其中D使用Trowbridge-Reitz GGX,F使用Fresnel-Schlick近似(Fresnel-Schlick Approximation),而G使用Smith’s Schlick-GGX。

正態分佈函式

正態分佈函式D D ,或者說鏡面分佈,從統計學上近似的表示了與某些(中間)向量h h 取向一致的微平面的比率。舉例來說,假設給定向量h h ,如果我們的微平面中有35%與向量h h 取向一致,則正態分佈函式或者說NDF將會返回0.35。目前有很多種NDF都可以從統計學上來估算微平面的總體取向度,只要給定一些粗糙度的引數以及一個我們馬上將會要用到的引數Trowbridge-Reitz GGX:

在這裡插入圖片描述

在這裡h h 表示用來與平面上微平面做比較用的中間向量,而a a 表示表面粗糙度。

如果我們把h h 當成是不同粗糙度引數下,平面法向量和光線方向向量之間的中間向量的話,我們可以得到如下圖示的效果: 在這裡插入圖片描述

當粗糙度很低(也就是說表面很光滑)的時候,與中間向量取向一致的微平面會高度集中在一個很小的半徑範圍內。由於這種集中性,NDF最終會生成一個非常明亮的斑點。但是當表面比較粗糙的時候,微平面的取向方向會更加的隨機。你將會發現與h h 向量取向一致的微平面分佈在一個大得多的半徑範圍內,但是同時較低的集中性也會讓我們的最終效果顯得更加灰暗。

使用GLSL程式碼編寫的Trowbridge-Reitz GGX正態分佈函式是下面這個樣子的:

float D_GGX_TR(vec3 N, vec3 H, float a)
{
    float a2     = a*a;
    float NdotH  = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;

    float nom    = a2;
    float denom  = (NdotH2 * (a2 - 1.0) + 1.0);
    denom        = PI * denom * denom;

    return nom / denom;
}


幾何函式

幾何函式從統計學上近似的求得了微平面間相互遮蔽的比率,這種相互遮蔽會損耗光線的能量。

與NDF類似,幾何函式採用一個材料的粗糙度引數作為輸入引數,粗糙度較高的表面其微平面間相互遮蔽的概率就越高。我們將要使用的幾何函式是GGX與Schlick-Beckmann近似的結合體,因此又稱為Schlick-GGX:

在這裡插入圖片描述

這裡的k k 是α α 基於幾何函式是針對直接光照還是針對IBL光照的重對映(Remapping) :

在這裡插入圖片描述

k IBL =α 2 2 kIBL=α22

注意,根據你的引擎把粗糙度轉化為α α 的方式不同,得到α α 的值也有可能不同。在接下來的教程中,我們將會廣泛的討論這個重對映是如何起作用的。

為了有效的估算幾何部分,需要將觀察方向(幾何遮蔽(Geometry Obstruction))和光線方向向量(幾何陰影(Geometry Shadowing))都考慮進去。我們可以使用史密斯法(Smith’s method)來把兩者都納入其中:

在這裡插入圖片描述

使用史密斯法與Schlick-GGX作為G sub Gsub 可以得到如下所示不同粗糙度的視覺效果: 在這裡插入圖片描述

幾何函式是一個值域為[0.0, 1.0]的乘數,其中白色或者說1.0表示沒有微平面陰影,而黑色或者說0.0則表示微平面徹底被遮蔽。

使用GLSL編寫的幾何函式程式碼如下:

float GeometrySchlickGGX(float NdotV, float k)
{
    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;

    return nom / denom;
}

float GeometrySmith(vec3 N, vec3 V, vec3 L, float k)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx1 = GeometrySchlickGGX(NdotV, k);
    float ggx2 = GeometrySchlickGGX(NdotL, k);

    return ggx1 * ggx2;
}


菲涅爾方程

菲涅爾(發音為Freh-nel)方程描述的是被反射的光線對比光線被折射的部分所佔的比率,這個比率會隨著我們觀察的角度不同而不同。當光線碰撞到一個表面的時候,菲涅爾方程會根據觀察角度告訴我們被反射的光線所佔的百分比。利用這個反射比率和能量守恆原則,我們可以直接得出光線被折射的部分以及光線剩餘的能量。

當垂直觀察的時候,任何物體或者材質表面都有一個基礎反射率(Base Reflectivity),但是如果以一定的角度往平面上看的時候所有反光都會變得明顯起來。你可以自己嘗試一下,用垂直的視角觀察你自己的木製/金屬桌面,此時一定只有最基本的反射性。但是如果你從近乎90度(譯註:應該是指和法線的夾角)的角度觀察的話反光就會變得明顯的多。如果從理想的90度視角觀察,所有的平面理論上來說都能完全的反射光線。這種現象因菲涅爾而聞名,並體現在了菲涅爾方程之中。

菲涅爾方程是一個相當複雜的方程式,不過幸運的是菲涅爾方程可以用Fresnel-Schlick近似法求得近似解:

在這裡插入圖片描述 F 0 F0 表示平面的基礎反射率,它是利用所謂折射指數(Indices of Refraction)或者說IOR計算得出的。然後正如你可以從球體表面看到的那樣,我們越是朝球面掠角的方向上看(此時視線和表面法線的夾角接近90度)菲涅爾現象就越明顯,反光就越強:

在這裡插入圖片描述

菲涅爾方程還存在一些細微的問題。其中一個問題是Fresnel-Schlick近似僅僅對電介質或者說非金屬表面有定義。對於導體(Conductor)表面(金屬),使用它們的折射指數計算基礎折射率並不能得出正確的結果,這樣我們就需要使用一種不同的菲涅爾方程來對導體表面進行計算。由於這樣很不方便,所以我們預先計算出平面對於法向入射(F 0 F0 )的反應(處於0度角,好像直接看向表面一樣)然後基於相應觀察角的Fresnel-Schlick近似對這個值進行插值,用這種方法來進行進一步的估算。這樣我們就能對金屬和非金屬材質使用同一個公式了。

平面對於法向入射的響應或者說基礎反射率可以在一些大型資料庫中找到,比如這個。下面列舉的這一些常見數值就是從Naty Hoffman的課程講義中所得到的:

在這裡插入圖片描述

這裡可以觀察到的一個有趣的現象,所有電介質材質表面的基礎反射率都不會高於0.17,這其實是例外而非普遍情況。導體材質表面的基礎反射率起點更高一些並且(大多)在0.5和1.0之間變化。此外,對於導體或者金屬表面而言基礎反射率一般是帶有色彩的,這也是為什麼F 0 F0 要用RGB三原色來表示的原因(法向入射的反射率可隨波長不同而不同)。這種現象我們只能在金屬表面觀察的到。

金屬表面這些和電介質表面相比所獨有的特性引出了所謂的金屬工作流的概念。也就是我們需要額外使用一個被稱為金屬度(Metalness)的引數來參與編寫表面材質。金屬度用來描述一個材質表面是金屬還是非金屬的。

理論上來說,一個表面的金屬度應該是二元的:要麼是金屬要麼不是金屬,不能兩者皆是。但是,大多數的渲染管線都允許在0.0至1.0之間線性的調配金屬度。這主要是由於材質紋理精度不足以描述一個擁有諸如細沙/沙狀粒子/刮痕的金屬表面。通過對這些小的類非金屬粒子/刮痕調整金屬度值,我們可以獲得非常好看的視覺效果。

通過預先計算電介質與導體的F 0 F0 值,我們可以對兩種型別的表面使用相同的Fresnel-Schlick近似,但是如果是金屬表面的話就需要對基礎反射率新增色彩。我們一般是按下面這個樣子來實現的: vec3 F0 = vec3(0.04); F0 = mix(F0, surfaceColor.rgb, metalness);

我們為大多數電介質表面定義了一個近似的基礎反射率。F 0 F0 取最常見的電解質表面的平均值,這又是一個近似值。不過對於大多數電介質表面而言使用0.04作為基礎反射率已經足夠好了,而且可以在不需要輸入額外表面引數的情況下得到物理可信的結果。然後,基於金屬表面特性,我們要麼使用電介質的基礎反射率要麼就使用F 0 F0 來作為表面顏色。因為金屬表面會吸收所有折射光線而沒有漫反射,所以我們可以直接使用表面顏色紋理來作為它們的基礎反射率。

Fresnel Schlick近似可以用程式碼表示為: vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); }

其中cosTheta是表面法向量n n 與觀察方向v v 的點乘的結果。

Cook-Torrance反射率方程

隨著Cook-Torrance BRDF中所有元素都介紹完畢,我們現在可以將基於物理的BRDF納入到最終的反射率方程當中去了:

L o (p,ω o )=∫ Ω (k d cπ +k s DFG4(ω o ⋅n)(ω i ⋅n) )L i (p,ω i )n⋅ω i dω i Lo(p,ωo)=∫Ω(kdcπ+ksDFG4(ωo⋅n)(ωi⋅n))Li(p,ωi)n⋅ωidωi

這個方程現在完整的描述了一個基於物理的渲染模型,它現在可以認為就是我們一般意義上理解的基於物理的渲染也就是PBR。如果你還沒有能完全理解我們將如何把所有這些數學運算結合到一起並融入到程式碼當中去的話也不必擔心。在下一個教程當中,我們將探索如何實現反射率方程來在我們渲染的光照當中獲得更加物理可信的結果,而所有這些零零星星的碎片將會慢慢組合到一起來。

編寫PBR材質

在瞭解了PBR後面的數學模型之後,最後我們將通過說明美術師一般是如何編寫一個我們可以直接輸入PBR的平面物理屬性的來結束這部分的討論。PBR渲染管線所需要的每一個表面引數都可以用紋理來定義或者建模。使用紋理可以讓我們逐個片段的來控制每個表面上特定的點對於光線是如何響應的:不論那個點是金屬的,粗糙或者平滑,也不論表面對於不同波長的光會有如何的反應。

在下面你可以看到在一個PBR渲染管線當中經常會碰到的紋理列表,還有將它們輸入PBR渲染器所能得到的相應的視覺輸出:

在這裡插入圖片描述

反照率:反照率(Albedo)紋理為每一個金屬的紋素(Texel)(紋理畫素)指定表面顏色或者基礎反射率。這和我們之前使用過的漫反射紋理相當類似,不同的是所有光照資訊都是由一個紋理中提取的。漫反射紋理的影象當中常常包含一些細小的陰影或者深色的裂紋,而反照率紋理中是不會有這些東西的。它應該只包含表面的顏色(或者折射吸收係數)。

法線:法線貼圖紋理和我們之前在法線貼圖教程中所使用的貼圖是完全一樣的。法線貼圖使我們可以逐片段的指定獨特的法線,來為表面製造出起伏不平的假象。

金屬度:金屬(Metallic)貼圖逐個紋素的指定該紋素是不是金屬質地的。根據PBR引擎設定的不同,美術師們既可以將金屬度編寫為灰度值又可以編寫為1或0這樣的二元值。

粗糙度:粗糙度(Roughness)貼圖可以以紋素為單位指定某個表面有多粗糙。取樣得來的粗糙度數值會影響一個表面的微平面統計學上的取向度。一個比較粗糙的表面會得到更寬闊更模糊的鏡面反射(高光),而一個比較光滑的表面則會得到集中而清晰的鏡面反射。某些PBR引擎預設採用的是對某些美術師來說更加直觀的光滑度(Smoothness)貼圖而非粗糙度貼圖,不過這些數值在取樣之時就馬上用(1.0 – 光滑度)轉換成了粗糙度。

AO:環境光遮蔽(Ambient Occlusion)貼圖或者說AO貼圖為表面和周圍潛在的幾何圖形指定了一個額外的陰影因子。比如如果我們有一個磚塊表面,反照率紋理上的磚塊裂縫部分應該沒有任何陰影資訊。然而AO貼圖則會把那些光線較難逃逸出來的暗色邊緣指定出來。在光照的結尾階段引入環境遮蔽可以明顯的提升你場景的視覺效果。網格/表面的環境遮蔽貼圖要麼通過手動生成,要麼由3D建模軟體自動生成。

美術師們可以在紋素級別設定或調整這些基於物理的輸入值,還可以以現實世界材料的表面物理性質來建立他們的材質資料。這是PBR渲染管線最大的優勢之一,因為不論環境或者光照的設定如何改變這些表面的性質是不會改變的,這使得美術師們可以更便捷的獲取物理可信的結果。在PBR渲染管線中編寫的表面可以非常方便的在不同的PBR渲染引擎間共享使用,不論處於何種環境中它們看上去都會是正確的,因此看上去也會更自然。