您當前的位置:首頁 > 詩詞

基於粒子表示的實時流體模擬與渲染(下)

作者:由 秘密子 發表于 詩詞時間:2021-09-25

填一下之前的坑。前文及介紹可見 秘密子:基於粒子表示的實時流體模擬與渲染(上)

本文將主要討論流體的渲染及demo的管線設計方法,渲染基本原理為螢幕空間重建流體模型以實現流體的渲染。方法參考Screen Space Fluid Rendering[1]。

原理介紹

上文我們討論瞭如何用接近物理的方式去使用粒子模擬流體,本文討論的問題其實就是如何將這些粒子渲染的讓它們不那麼有顆粒感,而更像是一攤融合在一起的水。

目前對於粒子渲染成水有以下兩種常用方法:

將粒子看作大量metaball,重建流體mesh。該方案效果很好,一般影視級的流體渲染效果都是透過這種方法實現的,其缺陷是每幀都要重建mesh,效能消耗很大。

基於螢幕空間的表面重建方法,其效能消耗相對較少,無需重建完整的mesh,可以應用於實時場景。其缺點也很明顯,效果不如mesh重建方法,並且在相機位置變換、粒子在z軸方向分離時容易產生錯誤。

管線設計

基於粒子表示的實時流體模擬與渲染(下)

粒子渲染流體的完整管線

螢幕空間流體渲染分為以下幾步:

將粒子在螢幕空間內渲染為sphere,獲得深度圖、厚度圖

對深度圖做一次平滑操作,再計算流體的法線圖

根據法線計算流體的光照進行著色,同時根據得到的厚度圖進行區分著色。

與環境融合

根據這幾步關鍵步驟,即可設計出上圖所示管線。

螢幕空間的sphere渲染

基於粒子表示的實時流體模擬與渲染(下)

我們需要在螢幕空間渲染出單個的sphere來,光靠粒子的單個頂點是無法實現的,我們需要將其拓展為6個頂點(2個三角形)以渲染一個quad,從而得到我們的螢幕空間sphere。我的實現是採用Geometry Shader來生成形狀,再進入Fragment Shader裁去多餘區域。(Geometry Shader的消耗並不小,有條件的同學還是考慮在CPU完成生成Quad的操作)

如果讀者只希望傳單個頂點並採用幾何著色器生成quad的話,對於頂點的格式可以只儲存座標,以節省CPU與GPU之間的資料傳輸。

深度資料的生成

在生成Quad的同時,我們寫入深度data,透過解析的方式計算得到每個pixel的深度資料,從而在螢幕空間生成流體的深度圖。

法線資料的生成

基於粒子表示的實時流體模擬與渲染(下)

眾所周知,我們螢幕上看到的每個pixel其實都是從世界空間轉換過來的,所以想要生成法線資料,我們只需要將它們再轉換回去(事實上是轉換到Camera/View座標系即可),透過cross(ddx, ddy)的方法就能得到pixel的法線方向了。

基於粒子表示的實時流體模擬與渲染(下)

這裡需要注意的是,深度圖一定要採用32位的float儲存,否則重建出的法線會因為精度問題而失真。下圖為僅使用8位R通道儲存深度值進行重建的錯誤結果。

基於粒子表示的實時流體模擬與渲染(下)

錯誤的法線重建

深度圖平滑

如果我們直接對深度圖進行法線重建的話,得到的則會是上面的效果,我們可以看見一個個完整的球,這樣我們重建的流體也能看到一個個完整的球,這顯然不是我們的最終目標。所以需要採用某種方法對深度圖進行”模糊操作“來消除這些球之間的邊緣。因此,我們採用雙通濾波的方法進行操作。

為啥不用高斯模糊呢?因為高斯模糊會把邊緣也給模糊了,導致表面重建失真。

平滑後的法線如圖所示,可以生成比較合理的流體表面。

基於粒子表示的實時流體模擬與渲染(下)

流體的渲染

我們簡要地回顧一下對帶光照模型的物件如何進行渲染,我們需要它的albedo、normal、以及額外的emission等資料。目前我們已經得到了normal data,而albedo也可以透過固定值設定,於是我們已經可以渲染出流體。實際的渲染方程同樣可以參考Screen Space Fluid Rendering[1]一文,其渲染時還加入了Skybox Reflection和Fresnel。

然而這樣渲染出的流體有一個問題,就是其各個區域顏色都趨於一致。這裡我們就要請出厚度圖來實現albedo顏色的區分。

厚度圖的使用

厚度圖,顧名思義是定義了用於檢視每個pixel上堆積了多少“流體粒子“的圖,其渲染方式與深度圖渲染類似,只是我們渲染的sphere被替換為具有alpha的高斯模糊sphere,並採用additive的方式疊加渲染。

基於粒子表示的實時流體模擬與渲染(下)

大概像這個樣子

於是我們可以得到一整張粒子分佈頻率的厚度圖。

基於粒子表示的實時流體模擬與渲染(下)

在Screen Space Fluid Rendering[1]中,作者根據Beer‘s Law實現了Volumetric Absorption,從而使流體在不同區域表達出的顏色不同。下圖為本文實現的著色方法實現的最終效果。

根據Beer’s Law,顯示的最終顏色為I = exp(-kd),k為常量,d為厚度。我們在rgb channel上可以選擇不同的k值以達到不同的顏色渲染效果。

基於粒子表示的實時流體模擬與渲染(下)

Others

以上內容討論的都是流體本身的渲染,而事實上要將環境內容一起渲染進來的話需要拆為多個pass。例如我們希望可以調節流體的透明度、不同牆面的透明度等等。同時渲染邊界是一個問題——本文中物理模擬的環境是採用SDF生成的邊界,如何將SDF渲染出來同樣也存在問題。這裡就額外討論一下這些內容。

加入環境內容的渲染順序

基於粒子表示的實時流體模擬與渲染(下)

在本人的demo中,邊界、流體本身都是可以調節其自身透明度的。這就要求我將邊界物件作為transparent物件進行渲染並寫入深度,同時在渲染流體的時候不寫入深度。每次渲染邊界都需要先寫入深度,再實際進行渲染。在圖中可以看到,渲染永遠是先畫邊界背面、再畫流體、最後畫邊界正面,以此我們可以順利地將環境內容和流體一起正常地渲染出來。

SDF的多層渲染?

相信有部分讀者開始產生疑問了,我的邊界都是透過SDF生成出來的,那為何還能存在正反面的說法?難道不是一根射線射中了就停下來了嘛?只能射中一個面,後面的面就無法處理了呢。事實這也是我一開始在渲染邊界時產生的煩惱,之後我想了個土辦法,就是對每個SDF區域我都去對應地生成一個模型,用CSG[2]的表達方式讓建模軟體幫我做這個事。

基於粒子表示的實時流體模擬與渲染(下)

實際實現中,我是使用了Blender配合指令碼以達到我需要的效果。

基於粒子表示的實時流體模擬與渲染(下)

生成環境邊界模型的程式碼

後記

Screen Space Fluid Rendering[1]一文其實已經將大部分技術細節討論的很清楚了,需要實現的朋友其實參考這篇文章也能將效果實現出來。本文也只是對這篇文章拙劣的轉述,僅作為本人實現過相關效果的記錄。

以下影片是本人在今年3月時候錄製的,可以簡單看一下最終實現的流體效果。

基於粒子表示的實時流體模擬與渲染(下)

https://www。zhihu。com/video/1425140691592318976

參考文獻

[1] S。 Green, “Screen Space Fluid Rendering for Games,” in Game Developers Conference, San Francisco, 2010。

[2] “Constructive solid geometry,” [Online]。 Available:

https://

en。wikipedia。org/wiki/C

onstructive_solid_geometry

標簽: 渲染  流體  深度圖  生成  重建