以前看到了學長的文章git
(https://zhuanlan.zhihu.com/p/61962884)github
web
我才知道,環境光強無敵呀!算法
因此我本身也來實現一下。編程
這篇文章,我會把公式的由來,推導和理解都儘可能講清楚,作到知其然和知其因此然。故文章取名「深刻理解」。微信
// 不是很是深刻,應該是淺入(保命app
同時我會給出離線渲染和實時渲染中的基於圖像照明(Image-Based Lighting, IBL)的實現。dom
目錄
基礎編輯器
反射方程ide
菲涅爾係數 F
法線分佈函數 D
幾何函數 G
金屬工做流
基於圖像的照明(Image Based Lighting, IBL)
離線 IBL
別名方法 Alias Method
採樣
實時 IBL
irradiance map
specular
pre-filtered environment map
BRDF LUT
綜合
1. 基礎
1.1 反射方程
這個與上篇文章高度重複。
由於補充了一些細節,主要是各公式的發展歷程,因此這裏再討論一遍
反射方程[0]
是雙向反射分佈函數(Bidirectional Reflectance Distribution Function, BRDF),用於描述反射規律。
基於物理的渲染(Physically Based Rendering,PBR)是指使用基於物理原理和微平面理論(microfacet theory)建模的着色/光照模型,以及使用從現實中測量的表面參數來準確表示真實世界材質的渲染理念。

體如今反射方程上,就是要求 要基於物理原理和微平面理論。
經常使用的微平面模型爲 Cook-Torrance[1] 模型
其中 是漫反射項,
是鏡面反射項,係數
和
保證了能量守恆。
如下分別講講 、
和
的具體形式。
1.1.1 菲涅爾係數 F
是菲涅爾係數[2],用於描述反射率(值的範圍是 0 - 1)。
原公式比較複雜,Schlick 給出了簡化版本[3]
虛幻給出了更加速的版本[4]
關鍵是 exp2 這個內建函數,比 pow 快
1.1.2 法線分佈函數 D
是法線分佈函數(Normal Distribution Function),用於描述微平面法線分佈。Cook-Torrance[1] 使用的是 Beckmann 分佈[5]
Walter et al.[6] 給出了「更好」的法線分佈函數 GGX/Trowbridge-Reitz,這也被迪士尼和虛幻[4]所採用
1.1.3 幾何函數 G
是幾何函數(Geometry Function),用於描述幾何遮蔽(Geometry Obstruction)和幾何陰影(Geometry Shadowing)(值的範圍是 0-1)。

通常還用 Smith shadowing-masking approximation 來簡化[7]。
可由
導出,故通常二者是相對應的。
GGX 對應的 比較複雜,考慮簡化一下。
以前提到的 Schlick[3] 的簡化公式是基於 Beckmann 的
其中 。
將 Beckmann 改成 GGX ,須要作以下改動[4]
另外,對於解析光源(點光源、方向光、聚光燈),咱們將粗糙度重映射[4]爲
注意,只對於解析光源進行該操做,本文只涉及 IBL,因此不須要考慮這個。完整的 PBR 計算裏纔會用到。
1.2 金屬工做流 Metallic Workflow
以前提到的 Schlick 版本的菲涅爾公式,只能用於描述電介質(如玻璃,水)。對於導體(如金屬),要使用另外一個菲涅爾公式。這就很麻煩。金屬工做流則使用參數金屬度(metalness)來肯定金屬性。爲了能讓菲涅爾簡化公式能同時用於金屬和非金屬,咱們可用金屬性來對 進行插值,非金屬的
默認近似爲 0.04,金屬的
則爲表面顏色。
vec3 F0 = vec3(0.04);
F0 = mix(F0, surfaceColor.rgb, metalness);
1.3 基於圖像的照明(Image Based Lighting, IBL)
IBL 至關於一個無限大的球面光源在照射場景。輻射度由 environment map 決定。

即 IBL 給出了函數
若是使用的是 2D 的 environment map,那要將方向向量 轉爲紋理座標
若是使用的是 cubemap,則直接採樣
2. 離線 IBL
這裏不對離線渲染算法 path tracing[0] 作介紹
在 path tracing 中實現 IBL,關鍵就是對其進行重要性採樣。
pbrt[8] 給出的實現是經過 2D 的二分查找來採樣像素,時間複雜度爲 。
還能夠更快,我下邊給出時間複雜度爲 的採樣算法。
2.1 別名法 Alias Method
別名法[9]能夠實現對有限的離散隨機變量快速採樣,時間複雜度爲 。而 environment map 從像素的視角上看,就是一個離散的隨機變量。因此咱們能夠用別名法來加速這個採樣過程。
一個像素的採樣機率由亮度和像素在球面上的面積(正比於 )決定,以下
其中 是亮度,
用像素中點對應的
來近似,通過歸一化後便可獲得它們的具體機率值。
算法請參考[9],具體實現以下
(https://github.com/Ubpa/RenderLab/blob/master/src/CppUtil/Basic/Sampler/AliasMethod.cpp)
2.2 採樣
首先用別名法採樣離散的像素。而後咱們繼續在像素內均勻採樣。這樣紋理座標的機率爲
在 path tracing 中,咱們須要的是 ,其與
的關係[8]爲
具體實現以下
(https://github.com/Ubpa/RenderLab/blob/master/src/CppUtil/Engine/Light/InfiniteAreaLight.cpp)
3. 實時 IBL
對於 Cook-Torrance 模型,咱們要解的方程爲
方程拆成了兩部分,左邊是漫反射項,右邊是鏡面項。對於 IBL,位置假設不變,因此有
則方程簡化爲
離線渲染中咱們能夠用蒙特卡洛積分法去求解這個積分值,但計算量很大。
因此實時渲染中面臨的問題就是如何快速計算。
主要思路就是預計算,把複雜的積分都先算好。咱們會分別預計算漫反射項和鏡面項,最終在實時渲染中只需經過簡單的紋理採樣便可獲得結果。
3.1 irradiance map
漫反射項爲
由於 ,所以咱們不能將其移出積分式。
爲了簡化,咱們對其作了近似
其中 爲
相比於 Schlick 的菲涅爾公式,就是 替代了本來的
。
這樣,漫反射項可簡化爲
如今的積分項只取決於法向 (半球
是法向所在的半球),注意,並非取決於視線方向

所以咱們能夠預計算這個積分值,獲得一個 cubemap,稱爲 irradiance map

積分方法就是蒙特卡洛積分,咱們能夠簡單的在半球面上均勻採樣。
對於均勻採樣的蒙特卡洛積分,公式爲
注意咱們把 也放到了 irradiance map 中。這樣在使用它時咱們就沒必要除以π。
使用 irradiance map 時根據法向 採樣,而且乘上
就能夠獲得漫反射項。
這個預計算能夠 CPU 算,固然也能夠用 GPU 來算,這裏附上 glsl 的實現
(https://github.com/Ubpa/RenderLab/blob/master/data/shaders/Engine/IBL/irradiance_convolution.fs)
3.2 speular
鏡面項爲
這個值取決於 ,
,此外還有 BRDF 中的粗糙度和金屬性,總共有 6 個維度(方向向量用球面座標表示的話就是 2 個維度)。很是多,因此咱們無法像漫反射項那樣直接用一個 cubemap 解決問題。
首先考慮最簡單暴力的方法。咱們直接在實時渲染中用蒙特卡洛積分來求解
可是爲了快速計算,咱們無法過多采樣。但採樣較少的話偏差又很大。因此這個方法在實時渲染中比較糟糕。
虛幻給出了近似解決的方法 spilt sum approximation[4]
虛幻沒有給出具體解釋,解釋能夠參考[10]。這裏簡述一下。
首先咱們想把 L 從積分中去除
![]()
假設,用法線分佈函數來進行重要性採樣,結果化簡得(省略了步驟)
![]()
這也就是 Unity 的實現。
可見,虛幻用權重函數 替代了
。
3.2.1 pre-filter environment map
如今咱們要計算
然而此時的 仍是由三個變量
、
和粗糙度(以前咱們用法線分佈函數來重要性採樣,而法線分佈函數中含有粗糙度)決定。所以首先咱們假設
。這樣引入的偏差,使得咱們在掠射角(grazing angles)看表面時無法獲得拖長的反射[4]。

這樣變量還剩 和粗糙度。對於一個特定的粗糙度,變量就只剩
,所以咱們能夠像 irrandiance map 那樣,預計算獲得一個 cubemap。咱們均勻的取粗糙度爲 0, 0.25, 0.5, 0.75, 1.0,這樣獲得 5 個 cubemap。實時渲染時,就用
和粗糙度進行三線性插值。
這樣咱們獲得的 5 個 cubemap,就是 pre-filtered environment map

由於咱們假設了 ,這樣反射方向
也與它們相等。因此,在 pre-filtered environment map 的視角里,三者是等同的。但實時渲染中,咱們在使用 pre-filtered environment map 的時候,三者又是不等同的。
那麼,咱們到底該用 、
仍是
去採樣呢?
咱們應該用 去採樣。
// 這是假設後帶來的結果,自行理解一下。別問爲何,問就塞口球
這個預計算能夠 CPU 算,固然也能夠用 GPU 來算,這裏附上 glsl 的實現
(https://github.com/Ubpa/RenderLab/blob/master/data/shaders/Engine/IBL/prefilter.fs)
3.2.2 BRDF LUT
上邊說到,積分改成了
其中的 咱們已經估計好了,如今就剩
這個積分含有變量 、
、金屬度和粗糙度。
因爲咱們使用的 GGX 是各向同性的,因此咱們只須要知道 和
的夾角
便可。
這樣咱們還剩三個變量 、金屬度和粗糙度。金屬度隻影響菲涅爾係數
。所以咱們能夠考慮將
移出積分式
咱們將積分拆成了兩部分 scale 和 bias,它們都消去了 ,所以變量只剩
和粗糙度,爲了方便,使用
作變量(這樣,這些變量的範圍都是
)。
scale 和 bias 能夠用根據法線分佈函數進行重要性採樣的蒙特卡洛積分來解決。
bias 也相似。
這兩部分均可以用一個 2D 的紋理來表示預計算的結果,它們都是 1 維變量,能夠放在同一張紋理裏邊,scale 放在紅色通道, bias 放在綠色通道。這個紋理稱爲 LUT。
這個 LUT 是由 BRDF 決定的,因此肯定的 BRDF 就有肯定的 LUT。咱們用的 BRDF 對應的 LUT 爲

這個預計算能夠直接用這張圖。本身也能夠用 CPU 算,固然也能夠用 GPU 來算,這裏附上 glsl 的實現
(https://github.com/Ubpa/RenderLab/blob/master/data/shaders/Engine/IBL/prefilter.fs)
3.3 綜合
至此,咱們拿到了三個紋理 irrandiance map、pre-filtered environment map 和 LUT。咱們能夠用它們實時地計算 。
核心代碼以下
vec3 F0 = mix(vec3(0.04), albedo, metalness);
vec3 F = F_roughness(wo, normal, F0, roughness);
vec3 kS = F;
vec3 kD = (1 - metalness) * (vec3(1) - kS);
vec3 irradiance = texture(irradianceMap, norm).rgb;
vec3 diffuse = irradiance * albedo;
vec3 R = reflect(-wo, norm);
const float MAX_REFLECTION_LOD = 4.0;
// 用 R 來採樣
vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;
vec2 scale_bias = texture(brdfLUT, vec2(max(dot(norm, wo), 0.0), roughness)).rg;
vec3 specular = prefilteredColor * (F0 * scale_bias.x + scale_bias.y);
vec3 ambient = kD * diffuse + specular;
結果
實時 IBL

離線 IBL

對比圖

(以後找模型再渲染幾個圖來
總結
IBL 的效果然的很好,用 metalness 和 roughness 就能夠控制材質,很是方便。
但這些只是近似結果,最大的偏差來自於 spilt sum approximation,也就是說,反射效果會有較大的差別。
還有不少不少的變化形式,但總的思想是不變的,都是爲了實時渲染時能快速求解渲染方程。
按照國際慣例,最後貼一下我最近寫的渲染引擎 RenderLab
(https://github.com/Ubpa/RenderLab)


包含了離線渲染,實時渲染,編輯器的功能,將來還會做爲遊戲引擎。
本文是個人畢業記念
所以本大四老鹹魚想在這裏簡單回憶一下過去

本着對 MMD 的興趣,大三下選修了圖形學
woc,課程做業量超大,一週一個編程實驗,總共有 9 個做業,肝的難受
// 附上做業連接 github/Ubpa/CG_LGLiu,感興趣的同窗能夠了解下
而我以前不多寫代碼,學了這門課我才知道,編程真有意思,圖形學真有趣

// 沒錯,我以前不怎麼碼代碼,太蔡了,QAQ,github 一片灰
最後,課程項目是隨便作一個東西,我就嘗試作了低仿崩崩崩(抄襲,苟命)
(https://www.bilibili.com/video/av24106016/)

當時的我還經驗淺薄(固然如今也是),什麼都不懂還必須得強行上,勉強交上了一份答卷

當時我本打算讀研選 AI 方向,但一番抉擇事後仍是選擇圖形學吧。
將來兩至三年,繼續學習,磨練技術(禿頭√)

感謝你們的閱讀
參考
[0] Kajiya, James T. (1986),"The rendering equation"(PDF),Siggraph 1986: 143–150.
[1] R. Cook and K. Torrance. "A reflectance model for computer graphics". Computer Graphics (SIGGRAPH '81 Proceedings), Vol. 15, No. 3, July 1981, pp. 301–316.
[2] (wikipedia) Fresnel equations
[3] Schlick, C. (1994)."An Inexpensive BRDF Model for Physically-based Rendering"(PDF).Computer Graphics Forum.13(3): 233–246.
[4] Karis B, Games E. Real shading in unreal engine 4[J]. Proc. Physically Based Shading Theory Practice, 2013, 4.
[5] Petr Beckmann, André Spizzichino, The scattering of electromagnetic waves from rough surfaces, Pergamon Press, 1963, 503 pp
[6] Walter B, Marschner S R, Li H, et al. Microfacet models for refraction through rough surfaces[C]//Proceedings of the 18th Eurographics conference on Rendering Techniques. Eurographics Association, 2007: 195-206.
[7] SMITH B. G.: Geometrical shadowing of a random rough surface. IEEE Trans. on Antennas and Propagation (1967), 668–671
[8] From Theory to Implementation, pp. 845-851
[9] (wikipedia) Alias method
[10] Alternative Take on the Split Sum Approximation for Cubemap Pre-filtering
聲明:發佈此文是出於傳遞更多知識以供交流學習之目的。如有來源標註錯誤或侵犯了您的合法權益,請做者持權屬證實與咱們聯繫,咱們將及時更正、刪除,謝謝。
做者:Ubp.a
來源: 知乎
More:【微信公衆號】 u3dnotes
本文分享自微信公衆號 - Unity3D遊戲開發精華教程乾貨(u3dnotes)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。