前言
這周本來的選題是關於模糊(Blur)效果的。由於這個系列一直力求的是精準地概括一些有趣的渲染知識,因此查資料後放棄了只談模糊——因為模糊的N種分類及其原理(文末會給出鏈接),已經有大神談得又準確又易讀了。
因此這一期轉而介紹實時渲染中一個組合了多種中間技術的效果——體積光散射,對應到太陽作為光源時就往往被稱為太陽照射(散射)效果,也俗稱太陽光束。
這一方案的實現過程中集合了陰影紋理映射、光線步進、模糊效果及屏幕空間後處理的思想,這些思想的基本原理可能不難理解,複雜的是其後續不斷演進迭代的技術方案。
1 Blur
模糊效果(Blur)的應用想必大家都不陌生,例如遊戲裡轉視角時會有動態模糊等。簡單來說模糊是一種基於屏幕空間的後處理效果,即對於渲染出的結果進行逐像素的修改——主流的模糊算法往往都是基於一個模糊核(Kernel)逐像素進行計算的結果,部分算法依據其思想還會有動態範圍的模糊核。但無論是哪種方式,其核心思想都可以理解成把一個或少量像素的顏色以更廣泛分散的權重“抹”到更大一片像素區域上得到的結果。
這裡展示幾種常見的模糊算法,具體的算法都可以去看後面提供的資料:
1)高斯模糊——Gaussian blur
(圖中展示了高斯模糊的應用原理,紅色是原像素,藍色是變換後的像素)
(圖中展示了高斯模糊的效果)
高斯模糊是一個低通道過濾(Low-pass filter)方案。除了通過模糊柔化畫面等應用外,高斯模糊還可以用來做邊緣查找;另外,在其基礎上還演變出了Bloom視覺效果,這一點應該也都不陌生。
(圖中是Bloom效果的簡要流程)
2 )KawaseBlur
Kawase Blur最初專用於Bloom效果,但也可以推廣作為專門的模糊算法使用,且在模糊外觀表現上與高斯模糊非常接近。
(圖中是Kawase Blur在單個pass的運算方式)
(圖中是kawase Blur多次pass的運算方式)
從圖中可以看出,Kawase Blur的Blur Kernel是隨迭代次數移動的。另外,其多次pass後的渲染效果接近高斯模糊,但具有更好的性能。
基於這個思想後來還衍生出了雙重模糊(Dual Blur),主要是在採樣精度和採樣方式上進行了改進。
3)散景模糊——Bokeh Blur
之前有一篇文章提到過景深效果,因此景深效果的視覺原理就不介紹了。這裡的Bokeh Blur可以理解成用近似的失焦分佈方式來作為一個模糊核。
(Bokeh Blur的像素分佈可以理解旋轉擴散的,如圖所示)
(圖中展示了一些相機不同參數拍攝的散景模糊效果)
需要注意,僅僅從屏幕空間後處理的方式是無法達到上圖的效果的。Bokeh Blur可以整體將畫面模糊,但無法準確定位出清晰部分的範圍。
後續人們提出了光圈模糊(Iris Blur)作為組合方案,作為達成景深效果的近似。但這種方案劃定出的清晰區域仍然只是近似的。
(圖中展示了引入橢圓遮罩範圍後的光圈模糊效果)
4)徑向模糊——Radial Blur
徑向模糊可以給畫面帶來很好的速度感,其基本思想是按一定的空間方向進行採樣擴散,得到的視覺效果是很多“線”。
(圖中展示了圖形選件中做出的徑向模糊效果)
後面提到的基於後處理的太陽光束方案就改進自Radial Blur,因為在模擬某一點發射出的很多線的形式上是很類似的。
5)動態模糊——Motion Blur
動態模糊與前幾種模糊不同,其算法依據的數據不僅僅是當前幀渲染的像素及幀緩衝。
常見的動態模糊有兩種方式,一種是對於歷史幀的混合,另一種是基於逐像素的速度向量( Velocity Vector)進行推算。後者需要的幀緩衝更少。
(圖中左側是加入了動態模糊的效果)
2 Shadow Map
陰影紋理映射(Shadow Map)是一個運行時產生的逐幀更新的紋理,因此需要佔用一定緩存。對於傳統渲染管線來說,shadow map記錄的是某一光源發出的“可見性”(Visibility)採樣結果,所以會先以光源為“攝像機”進行一次陰影紋理的繪製(Shadow Pass),繪製範圍可以依據主攝像機的視錐體進行換算得出(即shadow map需要包含主攝像機能渲染出的所有物體)。
(圖中展示了常見的基於深度緩衝的shadow map原理)
只是此時不是進行著色而是記錄“距離光源最近的遮擋深度”。有了這一中間結果後,再在主攝像機著色時對比每一個片元計算距離光源的距離,並和shadow Map中存儲的深度進行比對,如果小於則說明被遮擋住了,就應該表現為在陰影中。(最早的shadow map還不是存儲的深度,由於過於久遠這裡就不提了)
繪製shadow map和普通的渲染繪製共享一些問題與解決方案,如剔除、空間劃分優化等。
基於中間各步驟換算的一些採樣精度損失(主要是因為像素是離散的),這一方案有一些會導致失真的問題,常常通過引入偏移量來近似解決。
遊戲引擎後續迭代出了級聯陰影紋理(Cascaded Shadow Maps)技術以提高局部陰影的精度,主要是針對採樣這一步提供了不同的採樣分辨率;另外這種方式表現出的陰影是“硬陰影”,要實現“軟陰影”是一套不同的算法(通常是 Percentage-Closer Filtering PCF),思路上很接近某種模糊算法,這裡不展開了。
由於其相對較大的計算量,shadow map方案僅僅適用於數量不多的實時光源,多小的額外光源如點光源即使是實時光照著色,也往往無法繪製實時陰影。
由於shadow map是一個大而化之的實時渲染方案,如果對細節精度有要求就會引入各種優化方案。例如類似落日時那種太陽光與地表近似平行的狀態下,表現其陰影很難做到很好的效果;再比如有些物體自陰影往往難有很好的精度,例如人體的服飾或頭髮的陰影等。相關的優化方案這裡不展開了。
3 Ray Marching
光線步進(Ray Marching)其實可以理解為是一種前光線追蹤時代的間接光照算法。需要說明的是,光線在參與介質(Participating Media)中的傳播是可能有更多次彈射和相當複雜的衰減模式的,而在實時渲染領域中所有對於光線彈射基本都進行了近似,例如認為只彈射一次、衰減通過給定係數推算等。
Ray Marching是完全基於屏幕(或者說攝像機視角)進行運算的,需要計算的對象全部都是由方程來表示,這個方程是用來表示空間中任何一個點與該物體表面的最小距離。因此我們可以拋開多邊形網格,直接通過方程計算建模得到完美平滑的效果,或者用來模擬液體、變形、融合、體積雲等等多邊形難以達到的效果。
其最基本思想就是基於符號距離函數(Signed distance Functions),以一定的步長策略進行採樣。雖然計算量偏大,但Ray marching確實可以在一次繪製的Shader片元函數中完成。基於這一策略可以一定程度上計算折射、反射、AO等光照效果。
這裡不展開講基於距離場的step策略(即使每次都步進固定距離這一算法也成立,只是很浪費),著重演示的參與介質中的著色策略——參與介質可以簡單理解成液體或者雲這種有一定內部結構但整體透光的物體。
(圖中展示了ray marching一次step關注的幾個方面)
圖中藍色的射線是marching的方向,粉色範圍是用函數定義的參與介質的範圍,橙色是從採樣點到光源的射線——需要通過這個橙色的射線得到和介質的交點,並計算光線在介質內的衰減。
(圖中演示了ray marching多步採樣的流程)
最後逐步迭代渲染得到的結果,實際上是基於黎曼和思想的一個用採樣來近似求積分結果的問題——最終的結果得到的就是從視點看來符合光傳播特性的一個顏色值。
光線步進通常有2種方向策略,分別是從後向前的和從前向後的,兩者的核心計算公式不同,但整體思想接近。一般認為從前向後的方案比較好,因為可以在運算結果已經完全不透明時就提前停下來。(這裡和之前講的顏色半透明混合略微不同,是一類採樣近似算積分的問題,因此可以從前向後)
*光線步進每一步的射線不一定只是向光源,例如之前講過屏幕空間反射就是向對應位置的片元。
更多細節都可以參考Wiki和其它資料,這裡也不會是這個系列第一次提到Ray Marching(最多隻能算開了個頭),但至少講到這裡構造一個視覺上是God Ray的要素都已經齊備了。
(圖中展示了通過Ray marching能達到的渲染效果)
4 Volumetric Light Scattering as a Post-Process
目前主流的遊戲引擎中內置的Sun Shaft方案是基於後處理的。
最常見的方案是將遊戲內的太陽(主光源)作為中心,以一定步長採樣光源並衰減(調整後的徑向模糊 )+ Bloom。 下面摘一段Nivdia的GPU Gem原文中的算法說明:
Given the initial image, sample coordinates are generated along a ray cast from the pixel location to the screen-space light position. The light position in screen space is computed by the standard world-view-project transform and is scaled and biased to obtain coordinates in the range [-1, 1]. Successive samples L(s, , i ) in the summation of Equation 4 are scaled by both the weight constant and the exponential decay attenuation coefficients for the purpose of parameterizing control of the effect. The separation between samples' density may be adjusted and as a final control factor, the resulting combined color is scaled by a constant attenuation coefficient exposure.
另外一個方案需要計算屏幕空間遮擋(Screen-Space Occlusion),其基本思想就是用一個Pre-Pass(或者是Stencil 模板方式)將光源前的遮擋物渲染或描述出來,在最終採樣光源並混合時在屏幕空間考慮遮擋物的影響。基於這些實現的方式,此方案在應對更多光源時或不同光源位置時有其相應的侷限。(這裡不展開了,後面會附加原文鏈接)
(圖中b表現了預渲染出的遮擋物信息)
(圖中展示了《巫師2》中基於這一方案得到的效果)
5 Volumetric Light Effects in Killzone:Shadow Fall
現在能找到的較早提出Ray Marching方案的光線散射來自《Killzone:Shadow Fall》分享的文檔。為避免個人概括水平不足錯失了原文的信息,這裡也摘錄一段基礎算法的介紹:
To render the volumetric light effects, we start by rendering a shape for each light that represents the light's volume. ... For the sunlight we will render a fullscreen quad, as the volume of the sunlight covers the entire scene. We render each shape with a special volumetric light shader. For each pixel the shader will calculate the line segment of the view ray that intersects the light's volume. We then enter the ray-march loop, which takes multiple light contribution samples across this line segment. Artists can define the number of ray-march steps we take on a per-light basis. For each ray-march step the sample position is calculated, and the sample is lit using the same code we would normally use to light the geometry. When performing the lighting calculations, we assume we are lighting a completely diffuse white surface that is facing the light direction to get the maximum contribution of the light at the sample's position. Because we use the same code that is used to calculate the lighting for a regular surface, we automatically support all light features we have, such as shadow mapping, fall-off ranges, textures to define the light's color, etc. To improve rendering performance, we disabled all shadow filtering features.... Finally, all ray-march samples are added together, and the effect is rendered additively to the scene.
文章中提到了如何定義光照體積的範圍,以及為何渲染每一個採樣步驟時可以完全採用普通光照著色的特性(如Shadow map),最後採樣結果是被累加並以疊加(Additive)方式渲染到屏幕上。這段敘述大部分思路也和前面介紹到的Ray Marching章節一致,只是提到了更多技術細節——例如日光的體積範圍如何劃定(原文還提到了其它光源的劃定,這裡沒摘錄);如採樣shadow map時不需要考慮filtering就已經有足夠好的效果,這樣還可以提升性能等。
這部分引用略去了提到散射係數(Scattering Factor)的內容,這是考慮光照著色時需要考慮的細節算法內容,但這裡不展開了。
(散射係數用來表現光線穿過介質時的散射程度)
一般來說光線散射效果是基於特定散射介質的,但那又是另一個話題——即散射介質本身還有一些特性,例如自發光、光線通過係數與散射係數等,例如體積霧、體積雲等。
(圖示的遊戲中已經不僅是Sun Shaft,而是拓展到Light Shaft了,有很多光束)
結語
其實遊戲史上有不少遊戲,雖然不好賣或不那麼好玩,但確確實實帶來了圖形學上的創新與突破。典型的一個例子就是我們都熟悉的育碧的《刺客教條》系列,再比如本文中提到的《殺戮地帶》系列;雖然很多思想學術上早就有論文了,但恰如其時的引入工業界和民用又是另一回事了。如果聽過重輕老師的《遊戲帝國 第二季》,應該對這一點也能有體會。
如果一個遊戲要引入太陽光照表現一個相對真實的世界,基於後處理的太陽光束已經足夠使用(相匹配的效果還有鏡頭光斑 Lens Flare)。而如果要呈現更豐富限制更少的體積光渲染,由於要使用Ray marching,目前來說這類體積渲染效果對於移動端和低性能設備還是略顯昂貴,但也不是完全不能用;國內的一些多端遊戲,在PC端的採用的渲染管線技術往往是高於手機端的,就比如體積雲、動態AO、大氣散射等等,有些效果在手機性能更好時會逐漸下沉到手機端。
最後想談一個21世紀對待知識信息的觀念,這也是我自己近些年越來越深的感受:在這個信息爆炸的時代,人們可以很快接觸到過去60年甚至更長時間積累下來的知識,這一點在圖形學上就完全是如此,但肯定沒能力全部細緻的學完。因此,多大程度上對知識做概念性的把握,多大程度做細節原理的把握,其實這一直是一個不好把握的尺度——例如本篇談到Ray Marching,即使其基本思想不復雜,但延伸出應用和優化中又有著無窮的細節和流變,但是人的精力又不支持把它們全部搞懂,很多時候也沒有必要(因為技術演變過時,或是對個人經濟狀態沒有貢獻等原因)。
我個人的想法是,起碼要把握到算法思想一層,至於更深的數學層即使沒能力去把握,這也不妨礙隨時對一個技術方案拿起來就能融會貫通的使用和修改,同時也不會把根本的概念都理解錯誤。
這個文章系列是把我個人的這種理解又精簡成玩過遊戲的非從業人員也能通過文字看懂的信息,我希望每一篇都是比較有效的“長求總”。
最後是一些資料的鏈接:
知乎上大神的一篇講模糊的長文
Nvidia GPU Gem3書中動態模糊的篇章
Nvidia 早期的一篇分享多層ShadowMap的文檔
Nvidia GPU Gem3書中多層ShadowMap的篇章
講RayMarching比較詳細的一篇劍橋大學的文檔
ShaderToy中基於ray marching的經典蝸牛案例
Nvidia GPU Gem3書中對於後處理體積光散射的篇章
網絡讀書站引用的Volumetric Light Effects in Killzone: Shadow Fall(國內網上看到的翻譯多少 有些問題,建議看看原文)
The Eurographics Association 收錄的學術文章 Real-time Volumetric Lighting in Participating Media