計算機圖形學十五:基於物理的渲染(蒙特卡洛路徑追蹤)

(本篇文章同步發表於知乎專欄:https://zhuanlan.zhihu.com/p/146714484 歡迎三連關注)

摘要

在上一篇文章中,我們通過對輻射度量學當中一系列概念的定義,引入了渲染方程,一個正確的光線傳播模型,但並沒有去涉及如何解出該渲染方程,或者說如何通過該渲染方程計算出屏幕座標上每一個座標的像素值。在本篇文章中會利用蒙特卡洛路徑追蹤來完成這個目標。

1 蒙特卡洛積分(Monte Carlo Integration)

首先讓我們先搞懂蒙特卡洛路徑追蹤的這個“蒙特卡洛”的前綴到底指什麼。

蒙特卡洛積分的目的: 當一個積分很難通過解析的方式得到答案的時候可以通過蒙特卡洛的方式近似得到積分結果,如下圖所示:

顯然對於這樣一個函數,很難去用一個數學式子去表示,因此無法用一般解析的方法直接求得積分值,而這時候就可以採用蒙特卡洛的思想了。

蒙特卡洛積分的原理及做法: 對函數值進行多次採樣求均值作爲積分值的近似

該做法十分容易理解,想象一下如果對上圖這個函數值進行均勻採樣的話,其實就相當於將整個積分面積切成了許許多多個長方形,然後將這些小長方形的面積全部加起來。沒錯,該做法其實就與黎曼積分的想法幾乎一致。但蒙特卡洛積分更加的general,因爲它可以指定一個分佈來對被積分的值進行採樣,定義如下:

如圖所示,我們希望求出一個函數f(x)f(x)在積分域[a,b][a,b]上的積分值,選定一個採樣的分佈p(x)p(x),通過對該分佈來進行多次的函數值採樣,最後估計的值如圖中最下方式子所示。

這裏對該式子進行一個簡單的推導。相信大家都知道,求均值的做法其實也是對期望的逼近,因此:
1Ni=1Nf(Xi)p(Xi)Exp(x)(f(x)p(x))\frac{1}{N} \sum_{i=1}^{N} \frac{f\left(X_{i}\right)}{p\left(X_{i}\right)} \approx \mathbb{E}_{x\sim p(x)}(\frac{f\left(x\right)}{p\left(x\right)})
那麼對於這樣一個服從某一分佈的期望的計算套公式直接計算得:
EXip(x)(f(Xi)p(Xi))=abf(x)p(x)p(x)dx=abf(x)dx\begin{aligned} \mathbb{E}_{X_i\sim p(x)}(\frac{f\left(X_{i}\right)}{p\left(X_{i}\right)}) &=\int_{a}^{b} \frac{f(x)}{p(x)}p(x)d x\\ &=\int_{a}^{b} f(x)d x\end{aligned}
通過以上推導即可明白蒙特卡洛的近似正是對積分值的一個無偏估計。

但在本文中爲了方便,所有的採樣都使用均勻採樣,因此很容易推出:

因此,蒙特卡洛在此來說就是一個幫助求得困難積分值的方法

2 蒙特卡洛路徑追蹤(Monte Carlo Path Tracing)

回顧一下上篇文章中所得到的渲染方程:
Lo(p,ωo)=Le(p,ωo)+Ω+Li(p,ωi)fr(p,ωi,ωo)(nωi)dωiL_{o}\left(p, \omega_{o}\right)=L_{e}\left(p, \omega_{o}\right)+\int_{\Omega^{+}} L_{i}\left(p, \omega_{i}\right) f_{r}\left(p, \omega_{i}, \omega_{o}\right)\left(n \cdot \omega_{i}\right) \mathrm{d} \omega_{i}

要想解出以上方程的解主要有兩個難點:

  1. 積分的計算
  2. 遞歸形式

而解決這些難點自然就要利用上節中所提到的蒙特卡洛積分方法了。

在進入具體計算之前,對渲染方程做出一點小修改,即捨棄一下自發光項(因爲除了光源其他物體不會發光), 以方便進行計算推導:
Lo(p,ωo)=Ω+Li(p,ωi)fr(p,ωi,ωo)(nωi)dωiL_{o}\left(p, \omega_{o}\right)=\int_{\Omega^{+}} L_{i}\left(p, \omega_{i}\right) f_{r}\left(p, \omega_{i}, \omega_{o}\right)\left(n \cdot \omega_{i}\right) \mathrm{d} \omega_{i}

從具體例子出發,首先僅僅考慮直接光照:

Lo(p,ωo)=Ω+Li(p,ωi)fr(p,ωi,ωo)(nωi)dωiL_{o}\left(p, \omega_{o}\right)=\int_{\Omega^{+}} L_{i}\left(p, \omega_{i}\right) f_{r}\left(p, \omega_{i}, \omega_{o}\right)\left(n \cdot \omega_{i}\right) \mathrm{d} \omega_{i}

觀察該修改過之後的方程其實就只是一個單純的積分計算了,其物理含義爲着色點p到攝像機或人眼的Radiance值。

回想第一章所提的,對於一個困難積分只要選定一個被積分變量的採樣分佈即可通過蒙特卡洛的方法得到積分結果的近似值,而此時的被積分值爲ωi\omega_i,選定ωip(ωi)\omega_i \sim p(\omega_i),不難得出積分近似結果如下:
Lo(p,ωo)1Ni=1NLi(p,ωi)fr(p,ωi,ωo)(nωi)p(ωi)L_{o}\left(p, \omega_{o}\right) \approx \frac{1}{N} \sum_{i=1}^{N} \frac{L_{i}\left(p, \omega_{i}\right) f_{r}\left(p, \omega_{i}, \omega_{o}\right)\left(n \cdot \omega_{i}\right)}{p\left(\omega_{i}\right)}

正如一開始所說,先單獨考慮直接光照,因此只有當採樣的方向ωi\omega_i擊中光源的時候,光源纔會對該着色點有貢獻,計算僞代碼如下:

顯而易見的,單獨僅僅考慮直接光照自然是不夠的,還需要間接光照,即當採樣的ωi\omega_i方向碰撞到了別的物體,如下圖所示:

此時採樣的光線碰撞到了另一個物體的Q點,那麼該條路徑對着色點P的貢獻是多少呢?自然是在點Q的直接光照再乘上反射到該方向上的百分比了!顯然這是一個類似光線追蹤的遞歸過程,不同在於該方法通過對光線方向的採樣從而找出一條條可行的路徑,這也正是爲什麼叫路徑追蹤的原因,僞代碼如下:

至此,我們成功通過蒙特卡洛的方式解出了渲染方程的積分值,也通過考慮直接光照與間接光照解決了遞歸的問題。但該方法至此有一個非常致命的問題:

我們通過每次對光線方向的採樣從而解出方程,假設每次採樣100條,那麼從人眼出發的第一次採樣就是100條,在進行第二次反射之後就是10000條,依次類推,反射越多次光線數量便會爆炸增長,計算量會無法負擔,那麼如何才能使得光線數量不爆炸增長呢?唯有每次只採樣一個方向!N=1

每次如果只採樣一個方向那麼所帶來的問題也是顯而易見的,積分計算的結果會非常的noisy,雖然蒙特卡洛積分是無偏估計,但樣本越少顯然偏差越大。但該問題很好解決,如果每次只去尋找一條路徑結果不好,那麼重複多次尋找到多條路徑,將多條路徑的結果求得平均即可!如下圖所示:

改良之後的Path Tracing僞代碼如下:

通過對經過像素的光線重複採樣,每次在反射的時候只按分佈隨機選取一個方向,解決了只對經過像素的光線採樣一次,而對反射光線按分佈採樣多次所導致的光線爆炸問題。

那麼現在所有的問題都解決了嗎?還沒有!因爲shade函數的遞歸沒有出口,永遠不會停下。
但這裏並不沒有采用類似光線追蹤當中設定反射深度顯示的給出遞歸出口的方法,而是非常精妙的採用了俄羅斯輪盤賭(Russian Roulette)

給你一把左輪,兩發子彈,你不知道哪一發會真正的射出子彈,因此拿這把左輪射自己,你有4/6的概率活下來,這就是俄羅斯輪盤賭的概念。

將其應用在路徑追蹤當中,首先設定一個概率PP, 有P的概率光線會繼續遞歸併設置返回值爲Lo/PL_o /P,有1P1-P的概率光線停止遞歸,並返回0。這樣巧妙的設定之下光線一定會在某次反射之後停止遞歸,並且計算的結果依然是無偏的,因爲Radiance的期望不變,證明如下:
E=P(Lo/P)+(1P)0=L0E=P^{\star}(L o / P)+(1-P)^{\star} 0=L_{0}

shade函數的僞代碼變更如下,使得可以停止遞歸了:

至此,我們的路徑追蹤算法已經完成大半,只差最後一個小問題!現在的路徑追蹤效率非常的低下,如圖所示:

在每次計算直接光照的時候,通過均勻採樣任選一個方向,但很少會的光線可以hit光源,尤其當光源較小的時候,這種現象越明顯,大量採樣的光線都被浪費了。

因此在計算直接光照的時候改進爲**直接對光源進行採樣!**這樣所有采樣的光線都一定會擊中光源(如果中間沒有別的物體),沒有光線再會被浪費了。假設光源的面積爲A,那麼對光源進行採樣的 pdf=1/Apdf = 1/A (因爲pdfdA=1\int \operatorname{pdf} d A=1),但原始的渲染方程:
Lo(p,ωo)=Ω+Li(p,ωi)fr(p,ωi,ωo)(nωi)dωiL_{o}\left(p, \omega_{o}\right)=\int_{\Omega^{+}} L_{i}\left(p, \omega_{i}\right) f_{r}\left(p, \omega_{i}, \omega_{o}\right)\left(n \cdot \omega_{i}\right) \mathrm{d} \omega_{i}
很明顯是對光線方向ωi\omega_i進行積分的,如果想要對光源進行採樣的並依然使用蒙題卡洛的方法,那麼一定要將其修改爲對光源面積 dA的積分,換言之就是需要找到dA與dωi\omega_i的關係即可。如下圖所示:

關係式中的cosθcos\theta^{\prime}是爲了計算出光源上微分面積元正對半球的面積,之後再按照立體角的定義dω=dAr2\mathrm{d} \omega=\frac{\mathrm{d} A}{r^{2}},除以着色點x與光源採樣點x’距離的平方即可。於是根據圖中二者的關係可將渲染方程改寫如下:
Lo(x,ωo)=Ω+Li(x,ωi)fr(x,ωi,ωo)cosθdωi=ALi(x,ωi)fr(x,ωi,ωo)cosθcosθxx2dA\begin{aligned} L_{o}\left(x, \omega_{o}\right) &=\int_{\Omega^{+}} L_{i}\left(x, \omega_{i}\right) f_{r}\left(x, \omega_{i}, \omega_{o}\right) \cos \theta \mathrm{d} \omega_{i} \\ &=\int_{A} L_{i}\left(x, \omega_{i}\right) f_{r}\left(x, \omega_{i}, \omega_{o}\right) \frac{\cos \theta \cos \theta^{\prime}}{\left\|x^{\prime}-x\right\|^{2}} \mathrm{d} A \end{aligned}
這樣便成功從ωi\omega_i積分轉到了對光源面積A的積分,就可以利用蒙特卡洛的方法對光源進行採樣從而計算直接光照的積分值了,對於間接光照,依然採用先前的方法進行光線方向的均勻採樣。最終僞代碼如下,分直接光照和間接光照兩部分計算:

tips:計算直接光照的時候還需要判斷光源與着色點之間是否有物體遮擋,該做法也很簡單,只需從着色點x向光源採樣點x’發出一條檢測光線判斷是否與光源之外的物體相交即可,如圖所示:

最後以一張閆老師課程的path tracing作業的截圖作爲結束!

如果本文對你有幫助的話求點贊求收藏求一個大大的關注 😃 ,後序會持續更新課程筆記,感謝閱讀!

Reference

[1] GAMES101-現代計算機圖形學入門-閆令琪
[2] Fundamentals of Computer Graphics 4th

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章