前言
有時候想想物理法則真的很厲害,因為它有著去中心化的運行方式。計算機科技領域的算力在自然法則面前還差距很遠。
對光線追蹤有所瞭解的可能會知道,現有的實時光線追蹤技術還是大量建立在AI推測和降噪算法的基礎上,本身底層能做到的每幀採樣率(基於“蒙特卡洛積分”方式,需要一定的採樣數)其實很低。這個數量級要每幀模擬物理世界中的分子級別的運算還是不可能的。
例如衣服打溼的效果,玻璃上的雨水效果之類,這些視覺效果在遊戲引擎中是不存在基於物理法則的“水滴”的。前者通常是一個基於打溼程度的整體布料渲染係數變化(輔助一些雨滴粒子之類),後者則往往通過一個實時生成的透明效果層來實現。
在離線渲染中,水體的物理模擬可以通過彈球系統來進行,當單個彈球足夠小時其實已經比較接近水分子系統了;而在實時渲染中,水體能實現“看起來像”已經耗費了很多人類的智慧,這裡面也有巨大的效果和性能的trade off。各種不同的實時渲染水體幾乎還都是一事一議的來製作和優化的,例如杯子中的酒、空中的雨滴和大海中的波浪要考慮的問題就千差萬別。
從這個意義上來說,現在和今後很長一段時間內遊戲中其實不存在“真”的水體。所以很多時候玩家覺得遊戲中的水看起來假,其實就是開發者沒有足夠的能力或精力去處理水體這個課題,沒有做到很好的模擬和抽象;而那些水體做的好的遊戲,要麼是把它作為重要視覺目標來對待,要麼就是他們有著長足的積累和足夠的性能空間。
本文綜合了比較成熟但可能不是最新的一套水體方案,大部分內容拆解自幾篇SIGGRAPH論文和一套成熟的Unity水體插件(文末會附資料鏈接)。另外由於篇幅和個人閱歷的原因,本文和此係列的其它文章一樣也只能起到一個拋磚引玉的作用。
下面讓我們逐章節開始:
1 基礎形體:網格——Mesh
理論上最低限度可以表達水體的多邊形就是一個平面,上面運行著有一定噪聲擾動、法線擾動及光照計算的半透明(或不透明)著色。而稍微有點寫實程度要求的水體,則不可避免要使用多邊形網格來描述水面的形體。
由於水體渲染肯定是要大量使用多邊形網格的,一般來說會有這些基本原則:
- 最小頂點數
- 保證靠近視點的細節(LOD)
- 穩定性(不同幀之間)
- 在視點移動時不產生噪點
- 適配各種不同視點
這裡面很多原則其實和大世界地形渲染是共有的問題意識。下面介紹一種被稱為Geometry Clipmaps的設計及其後續改進的內容。
圖中展示了這種多層結構的基礎多邊形分佈
Geometry Clipmaps要點:
- 幾何(密度)延視點分佈
- 頂點位置始終對齊到世界空間的格子上(整體呈矩形分佈)
- 經過接合的幾何LOD層級(*這一點後續又有所改進,變成網格不是必須接合的)
下面通過一組GIF演示一下這個方案的基本效果:
隨視點變化的網格密度(LOD)
網格通過動態劃分來切換LOD
通過這一方案,就可以達到近處的網格密度高、遠處相對較低,整體比較有序接合的效果;後續要對這個網格系統做基於波函數或頂點變換的形變也能繼續進行。得益於現代GPU,實時運算這種數量級的多邊形變換對於PC和主機已經不是問題。
由於這個方案的網格區域也不是無限大的,對於最外層網格更外側的區域需要一定的外延填充策略。這個方案就簡單的做了一下外延拓展,如果對於紋理外延拓展有了解的應該對這個模式不陌生。
圖中展示了外延填充後的網格
最後再來看一下攝像機遠近對於網格縮放的影響:
基於遠近的網格縮放策略
之所以需要不斷動態折騰這套網格LOD,是因為畢竟算力還是應該分配到最合適的方面。這裡面還有一個部分重疊(Overlay)的問題,由於篇幅原因不展開了。
最後來對比一下這套方案和《神秘海域4》中採用的大地形CDLOD(Continuous Detail LOD)方案:
CDLOD——幾何距離;CDClipmaps——曼哈頓距離(座標里程)
*Taxicab distance直譯是出租車距離,其實國內更常見的叫法應該是曼哈頓距離(Manhattan Distance)。
CDLOD——幾何距離、四叉樹、不能保證邊緣幾何正確、LOD之間沒有形體關聯。
CDClipmaps——曼哈頓距離、最小化CPU消耗、邊緣幾何有明確方案、LOD間有形體關聯。
2 波和浪:模擬——Wave & Foam Simulation
有了網格系統後,海水的波和浪都是基於對網格的整體形變來運作的。只是一般來說波的影響範圍沒有浪那麼大,因此兩者也有著不同的預運算和實時運算方式;由於實時的流體計算還是開銷太大了,因此實時運算也往往採用波函數等簡化方案。
1)基於預計算紋理的波形
在《刺客教條3》中,開發者就通過離線預計算的FFT(Fast Fourier Transform 快速傅立葉變換,這裡就是表現成預計算紋理)模擬了逼真的海浪效果。
To produce the waves a statistical model based on fourier transform was used, based on Jerry Tessendorf’s work (simulating ocean water). The team precomputed two sets of tiling and cycling wave displacements. These were stored as rgb textures. The water textures were projected from the camera position, thus distant waves are smaller and closer waves larger. The resulting waves are an input for vertex buffers.
譯:為創建這些波浪我們採用了一個基於快速傅立葉變換的靜態模型,基於Jerry Tessendorf之前關于海洋水流模擬方面的成果。團隊預計算了兩組平鋪、可循環的波紋替代,並存入RGB紋理中。水體紋理從攝像機位置被按照近大遠小的策略進行投射。最終結果的波形被輸入到頂點緩衝數據中。
圖中是《刺客教條》當時實現的效果及紋理緩衝中的內容
*作為一款十年前的方案,這種波浪現在已經是單機甚至手機3D遊戲的起點甚至標配了。
2)基於波函數的波形
對於實時波函數,這裡介紹一個Gerstner波函數。它演進自正弦波函數,公式如下:
看起來玄乎,其實邏輯並不複雜,大概就是定義了一組波的陡度和間隔等參數,帶入進去可以得到一組波型(甚至法線也可以高效的計算出,這裡不展開了)。
圖中是通過Gerstner波函數得到的波形示例
*更多的2D波函數可以參考文末的鏈接。
3)基於預計算形體,以“粒子”方式作用的波形
有時候,波形也被預存成形體變換的數據(網格、深度圖、法線圖等),以類似“粒子”的方式作用到水體網格上。形體變換數據會基於參數輸入(玩家操作、風向等)、已有波浪和發射源相關參數等屬性進一步運算,在進行形變後得到最終結果。
下面視頻展示了基於“粒子”方式的波形示例:
海浪的情況部分和波類似,目前主要的成熟方案還是基於預計算的。
1)基於替代紋理(displacement texture)的海浪
例如在Jacobian的替代紋理方案中,採樣值大於1代表拉伸、小於1代表擠壓,而小於0的值代表海浪局部重疊(摺進去)。如果結合數據緩衝區,還能更好的實現海浪基於時間的指數級衰減效果。
基於替代紋理的海浪示例
2)基於形變數據(deformation data)的海浪
嚴格上說形變數據和替換紋理沒有質的變化,只是形變數據不一定保存成紋理的形式,且有著更豐富的數據維度。
這部分我直接摘一段SIGGRAPH2022中《地平線:西域禁地》的分享內容。
We began by doing a Houdini sim of a breaking wave. From that we extracted a cross-section for each frame. The upper image is an example. The cross section line is expressed as a deformation from a flat surface, as shown by the arrows in the lower image. The XYZ offset is converted into RGB, the thin green and purple strip underneath shows the result.
譯:起初我們在Houdini(一款3D圖形軟件)中做海浪的模擬,並從中導出了每一幀的橫截面——上圖就是一個示例。橫截面的線條代表了在一個平面的基礎上,應用基於下圖所示的箭頭為方向的形變。XYZ的偏移被轉換為RGB值,圖中的綠色和紫色的細線代表了這一結果。
We repeat the process for all the frames of the animation. Each frame gave us a horizontal strip of pixels. They’re assembled into a vertical stack to form a single 2D texture for the whole animation, as shown here. A couple of example frames are shown here, with the corresponding row of the deformation texture outlined.
譯:我們對(海浪)動畫的所有幀重複以上處理,每一幀都為我們提供了一條像素數據。它們被垂直堆疊整合到到一張2D紋理,如圖所示包含了整個動畫的信息。這裡展示了一組示例幀和它們對應的形變數據——紋理中對應被圈出的那一行。
*後面還提到了一些基於這個數據組織方案的具體困難和處理,這裡不展開了。這個方案的亮點是以2D紋理的方式,通過切片還原的方式存儲了海浪的形變。
形變數據的應用
The two waves shown here are moving left right towards the shore. The lefthand wave is a younger version with has lower animation parameters, the righthand one is older and starting to break. To create the shape we apply the cross-section deformation along the curve of the wavefront. The deformations take a flat water surface and bend it to match their cross section. The cross sections are shown at a few places, the number is the V coordinate of the deformation texture.
Looking at the righthand one, near the camera at 0.27 the water is just starting to rise, further along at 0.4 it starts to steepen, at 0.5 it’s curling over and turning into foam.
譯:這裡展示了左右兩道朝著岸邊推進的波形。左側的波形更新且有著更低的動畫係數,右側的波形更老並開始破碎了。通過沿著波陣面曲線應用橫截面形變數據,我們創建了海浪形體——這些形變作用把海平面彎曲變成了符合截面數據的形狀。圖中多處展示出了橫截面數據,其中的數字表示形變紋理中的V座標數據(代表在動畫序列中的位置)。
以右側的波浪為例,靠近攝像機在0.27數字的位置,海水即將抬升;更遠處0.4數字的位置海浪開始變陡;在0.5數字的位置海浪開始捲曲並變成浪花。
*關於波陣面散佈曲線和浪花形體細節這裡不展開了。
《地平線:西域禁地》呈現的水體海浪效果
3 水體表面渲染——Water Surface Render
水體渲染的基礎還是半透明(或不透明)著色,只是基於設計的視覺模型不同需要考慮的維度不同。
以《刺客教條3》中的“簡單”水體著色為例,有如下參數項:
- 漫反射——Diffuse
- 高光——Specular
- 法線貼圖——Normal map
- 反射——Reflection
- 深度和變色——Depth & tint
- 折射與次表面散射——Refraction and ‘SSS’
- 複雜的浪花——Complex Foam
下面通過一組圖片展示這些特性逐步疊加上去的效果:
基礎水體——漫反射、顏色
高光
反射——屏幕空間或CubeMap
折射+次表面散射(近似)
其中水的深淺主要是通過顏色和透明度的變化來體現的:
漸變紋理與深淺變化
而實時的SSS效果則往往是一種近似方案(trick)。例如在《刺客教條3》中他們的方案是:
To provide the effect the team used a simple SSS test based on viewing position, sun position and surface slope, this would then vary localized ‘shallowness’ or tint/opacity. It is remarkable how effective it is to fake SSS by just making the underside of back lit waves ‘less shallow’ (tint/transparent).
譯:為提供這一效果,團隊採用了一項簡單的基於視點位置、主光源位置和表面斜率的SSS檢測,以局部改變水體的“深淺度”或顏色與透明度。通過使波浪的背面“看起來更淺”的這一處理有著顯著的效果。
SSS的關\開對比
*育碧的這一方案中還引入了一個基於距離的不透明邊界,在邊界外的水體可以都以不透明方式來渲染,以提升性能。
4 海浪渲染:深度剝離——Depth Peeling
做稍微複雜的半透明渲染都離不開深度剝離(之前一篇介紹透明渲染的文章中也簡單介紹過)。簡單來說就是先基於深度從近到遠排序出若干個透明層,再對它們進行從遠到近渲染的過程。
下面比對一下深度剝離關閉與開啟的效果:
1透明層——無深度剝離
5透明層
可以看到不採用深度剝離時,無法正確表現浪花的背面及水體中更多的層次。
簡單歸納一下水體深度剝離的一些細節,如下:
- 先進行順序無關的水體渲染,獲得最靠前的水體層的深度。
- 在得到最大深度層時,從後向前逐層渲染——依據兩層之間是否在水體中計算吸收和散射值。
- (中間層)可以應用水下視覺的後處理效果。
- 渲染最前層採用傳統折射著色(非多層折射的),但有正確的散射值。
*水體深度剝離的特殊之處在於光線可以一段在水體,一段在水體外,相應的計算參數是不同的。
5 水下渲染——Under Water Render
對於觀察點在水體中的情況,額外還需要考慮體積渲染+後處理。在水體中看水面,和水面中看水中計算上沒有本質區別;有了水面層的渲染結果後,水體內部的體積渲染還需要考慮一些水下的視覺特徵:
1)水質特點——水下吸收度和散射度
簡單點的水下視覺可以用類似距離霧(Distance Fog)的方式來進行能見度過渡。
如果追求更接近物理和更精確的視覺,一般要充分考慮水質對於不同波長的吸收和散射狀態。
圖中展示了不同水質情況的散射和吸收狀況
要比較精確的做到這一效果在實時渲染領域還是比較困難的,一般都是通過經驗公式或對實驗結果做LUT(Look Up Table)來實現。
2)太陽光束(God Ray)
關於表現大氣中的體積光,之前我有一篇文章介紹過——簡單來說就有屏幕空間後處理和體積渲染兩種思路。
但是水體由於比較空氣來說是更復雜的散射介質,通常就無法通過屏幕空間後處理的方式加上太陽光束了;至於體積渲染的思路,則可以完全沿用實現大氣中太陽光束的那些方案。
3)焦散(Caustics)
圖中展示了焦散(Caustics)效果的示例
Games101課程的閆老師很不喜歡焦散這個翻譯,因為其實這是一種折射後“重聚焦”的效果。很多時候有些翻譯確實有一定偏差(如果聽過重輕老師說“碎拍”的翻譯應該也有同感)。
下面簡單介紹一種提供焦散效果的方式:
- 首先,在幀緩衝中渲染一張(從水面向水底的)環境紋理,RGB通道存儲世界空間的XYZ值,Alpha通道儲存深度
環境紋理示例
- 光線從水面折射點開始對環境紋理求交點,類似RayMarching思路,原理如圖:
從水面交點計算折射後,逐像素和環境紋理的深度比較
和環境紋理有交點,則射線檢測結束
- 基於這一折射後的映射關係得到焦散紋理:
焦散紋理示意
- 在渲染的場景中應用焦散紋理,就可以得到近似的焦散效果。
*這一方案當然也可以有很多可優化的方向,不過其計算範圍始終是紋理座標範圍,必要時以半精度或四分之一精度來運算都能有不錯的近似效果。
圖中展示了某個水下渲染組合方案的效果
結語
雖然努力覆蓋了很多方面,但其實大家也能發現這種概括既不全面又不詳細——確實如此,因為實際上無論是渲染技術還是學術上,想在其中比較困難的例如波浪模擬、水下渲染真實性這些方面有所突破,可能都要付出數年的研究。這也是為何人類社會需要論文的原因,有價值的科研論文才是人類科技研究傳遞的基礎。
由於篇幅原因本文還未涉及動態物體和水體的交互中的一些其它細節。例如人游泳,既然不能做到流體力學計算,那該如何進行近似而有效的模擬——諸如此類其實還會有很多課題(特別基礎的噴個形變粒子就糊弄過去的肯定是能實現的,但更復雜的動態要考慮的就多了)。這類一事一議的課題在實時渲染領域還有很多。
反過來說,如果水體只是提供一種氛圍,那麼不做波浪形體、不做水下效果都是很常見的情況。這也是有些遊戲通過卡BUG到了水下之後,發現並沒有水的原因。
最後是一些資料鏈接:
Crest: Novel Ocean Rendering Techniques in an Open Source FrameworkPPT下載
Crest的Unity工程
Rendering Water in Horizon Forbidden West的SIG頁
SIG2008的Real Time Physics 的PDF
Nvidia:Effective Water Simulation from Physical Models
Fast Terrain Rendering with Continuous Detail on a Modern GPU
Assassin’s Creed III: The tech behind (or beneath) the action
Underwater Rendering with Realistic Water Properties (PDF)
Real-Time Mixed Reality Rendering for Underwater 360° Videos
Real-Time Underwater Spectral Rendering(2024)
Wave_equation Wiki