[Rendering] 基於物理的大氣渲染

# 要解決的問題

以下我們推導單次散射(single scattering)的氣渲染模型,即光線從太陽發出過、只經過一次散射被改變方向後射入我們的眼睛。

下圖顯示了我們觀察大氣時的觀察路徑AB,我們想知道B點的大氣顏色:

來源:https://www.alanzucconi.com/2017/10/10/atmospheric-scattering-1/

這個過程實際上就是對視線路徑AB上每一點的光照貢獻進行一個積分,下面我們把問題一步步拆分。

## 問題一:P點的光照是什麼?

由於我們關心的是單級散射,那麼唯一的一次散射發生在路徑AB上的每一點P,那麼該點的光照強度Ip要怎麼求呢?它可以理解成是,從P點出發沿著光照方向與大氣邊緣的交點為C,從C點的光照(我們認為大氣層邊緣的光照就是太陽發出的光照)經過路徑CP衰減後到達P點的光照:

來源:https://www.alanzucconi.com/2017/10/10/atmospheric-scattering-1/

用公式表示就是:

其中,T項衰減係數(Transmittance),它表示在某段路徑上的對光照的衰減程度。該公式也可以被認為是零級散射(zero scattering),即不考慮任何散射事件、直接考慮經過衰減後光強。

## 問題二:在P點發生了什麼?

我們已知到達P點的光照強度為Ip,那麼光線在P點經過一次散射後、經路徑PA其他大氣粒子的衰減後,最終到達人眼的光照強度Ipa為:

其中,λ是波長,θ是圖中所示的光線入射方向和觀察方向的夾角,hP點的高度。通俗解釋下,S項散射係數(Scattering),它表示此次散射事件中有多少光被反射到了θ方向上,T項是衰減係數(Transmittance),它表示在PA路徑上的對光照的衰減程度。它們之間是有聯繫的,簡單來說正是因為大氣對光線不斷發生散射(S項)才導致經過一定路徑後光線會發生衰減(T項)。接下來對每一項進行解釋。

(1) 散射係數: S(λ,θ,h)

散射係數和粒子的大小和折射率有關,這裡就得解釋一下大氣粒子的分類。我們知道大氣中有很多不同種類的大氣分子/粒子,一般在大氣渲染模型里把它們分成兩大類。一種是小分子,例如氮氣和氧氣分子等,另一種是大粒子,例如各種塵埃粒子。之所以要進行分類是因為它們對光線有著不一樣的行為(這裡忽略對光的吸收,只考慮散射):

  • 小分子:指大小遠小於光線波長的粒子。小分子對光的散射在前後方向上分布比較均勻,通常會使用Rayleigh散射(Rayleigh Scattering)對它們進行建模。由於這些分子大小比波長還要小很多,因此光的波長也會影響Rayleigh散射的程度。
  • 大粒子:指大小遠大於光線波長的粒子。大粒子在發生散射的時候會把更多的光散射到前向,通常會使用Mie散射(Mie Scattering)對它們進行建模。由於光的波長相較於這些粒子大小來說是可以忽略的,因此我們認為Mie散射跟光線波長無關。

下面以Rayleigh散射為例,解釋S(λ,θ,h)的含義。這裡直接給出公式,在Rayleigh散射模型中,散射到θ方向上的比例為:

其中,λ是光的波長,n是粒子折射率,N是海平面處的大氣密度,ρ(h)是高度h處的相對大氣密度(即相對於海平面的密度,可以理解成h處真正的大氣密度與海平面處大氣密度的比值,因此它在海平面處值為1,隨著h增加不斷減小),我們一般會使用指數函數對它的真實曲線進行數學擬合(是一種近似擬合,不要和後面提到的指數函數弄混):

其中,H是大氣的整體厚度。

可以看出Rayleigh散射大致和波長的4次冪的成反比,波長越小(越靠近紫光)的光被散射得越厲害。所以白天的時候天空為藍色,因為藍光在大氣里不斷被散射,黃昏的時候天空會變紅,因為相比於白天,陽光此時要穿越更厚得多的大氣層,在到達人眼之前,大多數藍光都被散射到其他方向,所以剩下來的就是紅光了。

(2) 衰減係數:T(PA)

對於大氣粒子對光的散射,在傳播路徑PA上光線會繼續衰減。S(λ,θ,h)關心的是被反射到某一特定角度上的光線比例,那麼現在我們就需要考慮經過一次散射後,在入射方向上還「剩下來」多少光。

我們先來看一次衰減。假設I0在經過一次散射後損失了β比例的能量,則剩餘的能量I1為:

那麼經過一段傳播路徑PA後,餘下的能量為:

  • 簡單情況:如果β為定值,我們可以直接通過微積分得到衰減一段距離x後的剩餘能量:

  • 複雜情況:複雜情況是β是與波長λ和高度h都相關,即不能簡單地當成定值。此時,我們就需要用積分來表示了:

那麼問題是,β是什麼?在簡單情況下,也是很多遊戲里霧效的實現方法中,就拿一個類似霧效密度的參數來代替β。在複雜情況下,我們就需要考慮真實的β是如何計算的。

之前我們一直在討論的S(λ,θ,h)是在某方向上的散射係數,那麼β就是在所有方向上的總散射係數。我們還是以Rayleigh散射為例,如果我們對Rayleigh散射的S(λ,θ,h)做球面積分,得到總散射係數β(λ,h)為:

終於,我們的公式長度縮短成了兩項,不用再看密密麻麻的符號了!上式可以理解成,β(λ)是海平面處的散射係數,隨著高度增加,散射強度也不斷減小。因為大氣相對密度ρ(h)不斷減小。據此,我們可以改寫T(PA)部分,可以看出,β(λ)其實只跟波長相關,在傳播路徑上不需要進行積分,因此可以提到積分外部:

我們可以進一步改寫其中的積分項。對比簡單情況下的公式,積分項對應的其實就是路徑長度x,而這部分積分可以理解成光學距離——Optical Depth

同樣,我們也可以改寫S(λ,θ,h)部分:

其中,P(θ)表示在這些被散射的能量中,有多少比例被反射到了θ方向上,它有一個專有名字——相位函數(Phase Function),也被稱為Scattering Geometry。所以對於Rayleigh散射來說,它的相位函數就是:

至此,我們簡單推導了S項T項,這樣一開始的公式就可以寫成:

總結起來,公式表示的含義就是,計算觀察路徑AB上某一點P的光照貢獻:

  1. 光線從太陽出發,到達大氣邊緣的C
  2. 經過路徑CP上的衰減到達P點(0級散射
  3. P點發生一次散射,將一部分光散射到了觀察方向上(1級散射
  4. 這部分光又經過AP路徑上的衰減最終到達了我們的眼睛

## 問題三:路徑AB之間發生了什麼?

來源:https://www.alanzucconi.com/2017/10/10/atmospheric-scattering-1/

P只是AB上的某一點,我們需要對路徑AB上的每一點進行積分:

其中涉及到了散射係數β(λ)、相位函數P(θ)、光學距離D(PA)等。

## 小結

至此,我們完成了單級散射模型的簡單推導。但實際情況要複雜得多,在到達人眼之前,光線已經在大氣里被散射了無數次,而更高一級的散射總是可以靠它前一級的散射來定義,即是一個遞歸定義

上式的含義是,在A點、從觀察方向v、光線入射方向s貢獻的第i+1級散射(左側)等於,對觀察路徑AB上的每一點P的貢獻度進行積分:

  1. P點處發生最後一次散射,把光線從積分方向v散射到觀察方向v上(β(v,v)),然後經過T(PA)衰減後到達A
  2. 在最後一次散射前,對之前發生在P點、從方向v、光線入射方向s貢獻的第i級散射做球面積分,表示從各個方向v貢獻的第i級散射

回顧我們之前對單級散射的推導過程,其實就是i=0的情況。對於第0級散射來說,由於v不等於s的方向來說,第0級散射都為0,所以我們省略了公式里做球面積分部分。而在A點、從觀察方向v、光線入射方向s最後的光照結果應該是把所有級的散射都加起來:

如果是離線渲染,理論上我們可以求得上述公式的精確解。但對於實時渲染來說,第0級和第1級散射都是可以較為快速的求得精確解的,而更高級的散射要計算起來就非常划不來了。在早期的大氣渲染資料(例如GPU Gems 2)里,都是對第0級和第1級散射的求解和渲染。直到2008 Precomputed Atmospheric Scattering的提出,我們也可以實時計算更高級別散射的近似解了。

# 大氣渲染模型

具體應用到遊戲渲染里,我們一般會把大氣渲染分為兩個部分:一是天空背景的渲染,即skybox,二是大氣透視的渲染,也是我們俗稱的大氣霧效。在渲染的時候需要考慮光線入射方向s上的遮擋(陰影),通常使用傳統的shadow map就可以了。這樣我們就可以得到god ray效果。

## 天空背景

我們之前推導的公式可以直接拿來渲染天空盒,這裡就不重複了。多說一句,第0級散射可以理解成對「sun disk」的渲染。

## 大氣透視

大氣透視(aerial perspective)是渲染場景里物體距離攝像機遠近的很重要的效果,也是所謂的霧效。與渲染天空盒略微不同的是,我們還需要考慮反射地表顏色等。

依然是考慮在A點、沿著觀察路徑AB接收到的光照。這部分光照可以分為兩個部分:一是B點反射的地表顏色Rb經過路徑AB衰減後的光照,也就是第0級反射,二是路徑AB上由於散射貢獻的光照:

Textinction就是我們之前推導的T(AB)Iinscattter則是之前推導的多級散射模型。

## 優化

核心是使用LUT,例如可以T(AB)存到一張2D紋理里。

## 例子:簡化模型

大部分遊戲的視角只會在地表附近,因此可以使用簡化模型來渲染大氣霧效。簡化模型有兩個最明顯的特徵:

  • 使用直線距離d直接代替光學距離D(AB)
  • 基於上一點,我們可以使用恆定的散射係數β

(1)Unity的Exponential Fog

最簡單的例子應該就是Unity的Exponential Fogdocs.unity3d.com/Manual)了,公式表示就是:

其中的exp部分就是Textinction,即使用恆定的散射係數的簡化模型。但後面計算Iinscattter的部分其實是不準確的,可以說是「不怎麼基於物理」。

(2)The Blacksmith中的大氣散射

Unity的The Blacksmith給出了大氣散射部分的代碼(blogs.unity3d.com/cn/20assetstore.unity.com/pa)。這部分以後再補吧。

# 參考資料

推薦閱讀順序即為排序順序:

  • 入門(理解渲染方程)
    • Atmospheric Scattering系列教程:alanzucconi.com/2017/10,詳細解釋了基本原理和原始的「暴力解法」
    • 2003 Modeling Skylight and Aerial Perspective:mathinfo.univ-reims.fr/,主要是用來理解大氣渲染方程的
    • 2002 Rendering Outdoor Light Scattering in Real Time:developer.amd.com/wordpdeveloper.amd.com/wordp,加上之前的兩篇,這三篇看完對渲染方程應該就可以有比較深刻的理解了,可以自己手推
  • 進階(實現渲染方程)
    • GPU Gems 2:developer.nvidia.com/gp,經典文章,求解single scattering,雖然文章給出的代碼是比較原始的「暴力解法」,但後面給出了關鍵的優化思想,就是使用LUT
    • 2008 Precomputed Atmospheric Scattering:hal.inria.fr/file/indexebruneton.github.io/pre,除了single scattering還可以得到multiple scattering的近似解,性能也很好,目前是各大3A遊戲的標配,缺點是論文寫得太難懂,各種數學符號。好在作者給了源碼,在2017年的時候甚至優化了代碼,增加了可讀性和健壯性,良心作者
  • 實際應用(GDC)

Unity有很多插件的實現都是基於2008年的Precomputed Atmospheric Scattering,例如uSky(assetstore.unity3d.com/#)、Azure(assetstore.unity.com/pa)等,可以參考和優化。

推薦閱讀:

最具發展前景的第三代珍惜水果,鈣果在這裡等你
現在就是五運六氣所述大涼反至的氣候,古人的大智慧真是了不起
為什麼80%的新手都畫不好褶皺?——不懂自然科學?不是好的插畫師
八個宇宙秘密(最好的算命風水)
BBC的自然紀錄片,牆都不扶就服你!

TAG:自然科學 | 物理學 |