再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案

麒麟子TM發表於2022-03-28
再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


但該專案基於 Cocos Creator 延遲渲染管線,對專案和裝置要求較高,所以麒麟子專門準備了這個獨立的水面效果分享,希望能夠對大家有所幫助。

二、水面渲染流程

水面渲染技術非常多,不同段位的產品,對水面的要求不同。

毛星雲的《真實感水體渲染技術總結》這篇文章中,通過對一些 3A 大作的水面渲染進行分析,列出了非常多的技術要點,有興趣的朋友可以拜讀。

水面渲染技術從簡單到複雜來排序,可以分為以下三類:

平面著色

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


頂點動畫

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


流體模擬

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


本文實現的是基於平面著色的水面效果,雖然它並非高階效果,但卻是大部分 3D 專案中採用的方案。

基於平面著色的水面渲染主要涉及以下幾個部分:

反射

折射

水深效果

水岸柔邊

動態天空盒

法線圖與光照

岸邊浪花

由於時間關係,法線圖與光照與岸邊浪花暫未實現。

標準的渲染流程如下所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


可以看出,如果要實現所有效果,至少需要繪製場景4次。

由於這裡的深度圖只是和折射搭配使用,8位精度足夠用了,我們可以考慮借用折射圖中的 Alpha 通道來儲存深度資訊。

優化後的流程圖如下:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


三、反射貼圖渲染

麒麟子在《用實時反射 Shader 增強畫面顏值》中已經完整地剖析了實時反射相關原理,在此就不再敷述,有需要了解的讀者可直接點選檢視。

這裡主要講一講本 DEMO 中的實現步驟。

步驟1:使用程式碼新建一個 RenderTexture。

步驟2:建立一個節點,新增攝像機元件,並將 clearFlags、clearColor、visibility 屬性與主攝像機同步。

步驟3:設定反射攝像機的渲染優先順序,確保比主攝像機先渲染。

步驟4:將新建立的 RenderTexture 賦值給此攝像機的 targetTexture 屬性。

以上步驟的程式碼在 WaterPlane.ts 中,如下圖所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


步驟5:在 lateUpdate 中同步主攝像機引數。

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


步驟6:在 lateUpdate 中根據實時反射原理,動態計算攝像機關於主攝像機的映象位置和旋轉。

最終,渲染得到的 RenderTexture 如下:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


麒麟小貼士:

所有物體的材質,需要加入自定義裁剪面,裁剪掉水面以下的部分。

可以明顯看到,上圖中綠色物體的倒影,水面以下的部分是被裁剪掉了的。

四、折射貼圖渲染

折射渲染的原理非常簡單:

渲染水平面以下的部分到 RenderTexture

在水面渲染階段使用噪聲圖進行擾動,以模擬出水面折射效果

折射渲染的流程與反射渲染大致相同,只有兩個細小的差別:

用於折射渲染的攝像機所有引數均與主攝像保持一致即可

折射渲染階段,物體被裁剪掉的是水面以上的部分

下面我們來看看,本 DEMO 中關於折射的實現步驟。

步驟1:使用程式碼新建一個 RenderTexture。

步驟2:建立一個節點,新增攝像機元件,並將 clearFlags、clearColor、visibility 屬性與主攝像機同步。

步驟3:設定反射攝像機的渲染優先順序,確保比主攝像機先渲染。

步驟4:將新建立的 RenderTexture 賦值給此攝像機的 targetTexture 屬性。

以上步驟的程式碼在 WaterPlane.ts 中,如下圖所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


麒麟小貼士: 注意紅色線框部分,本 DEMO 中折射貼圖的 Alpha 通道用於標記深度資訊,所以需要確保 Alpha 通道的值為 255。

步驟5:在 lateUpdate 中同步主攝像機引數、位置、旋轉等資訊。

最終,渲染得到的 RenderTexture 如下:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


五、水面渲染

水面渲染主要利用了投影紋理技術,將頂點的投影座標轉化為 UV,對摺射和反射貼圖進行取樣。

由於使用了折射貼圖,我們的水面材質不需要開啟 Alpha 混合。

折射渲染

步驟1:根據投影座標計算出螢幕 UV。如下所示:

vec2 screenUV = v_screenPos.xy / v_screenPos.w * 0.5 + 0.5;

步驟2:取樣折射貼圖,可以得到如下渲染效果:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


左邊為正常渲染效果,右邊為標記了折射內容的效果

步驟3:使用噪聲圖對摺射進行擾動,可得到如下效果:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


反射渲染

步驟1:與折射渲染一樣,根據投影座標計算出螢幕 UV。

步驟2:取樣反射貼圖,可以得到如下渲染效果:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


步驟3:使用噪聲圖對反射進行擾動,可得到如下效果:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


菲涅爾混合

菲涅爾的計算公式從玉兔的邊緣光教程開始,到實時反射等場合,已經出現過很多次了。下面是核心程式碼:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


折射可以視為水體本色,利用菲涅爾因子與反射內容混合,即可實現一個帶折射和反射的水體效果。

虛擬碼如下:

finalColor = mix(refractionColor,reflectionColor,fresnel)

最終可以得到如下顯示效果:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


完整程式碼程式碼請檢視專案中的 effect-water.effect 檔案。

六、水深效果

從上面的動畫中可以看出,雖然折射和反射效果都有了。但畫風有些奇怪,完全沒有水面的感覺。

這是水面沒有深淺效果導致的。

我們來看看,如何獲取深度資訊,並根據深度資訊實現水深效果。

獲取深度資訊

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


從上圖中,我們可以清晰地看到,靠近岸邊的海水的顏色比遠處海水的顏色透明得多。

產生這種現象的主要原因,就是基於視線方向的水體厚度不同。

什麼叫基於視線方向的水體厚度,請看下圖:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


我們通常說的水體深度,是指在忽略視線因素的情況,水面到水底的高度差。

在不追究細節的情況下,我們可以簡單地使用高度差來作為水的深度。

一種可能的虛擬碼如下:

depth = clamp((g_waterLevel - v_position.y) * depthScale,0.0,1.0);

其中 depthScale 是我們的深度縮放因子,可以用來調節比例尺問題,以及水體能見度線性衰減速率。

而基於視線方向的水體厚度,是指視線方向與水平面和水底交點的距離差。即圖中 點 P1 到 點 P2 的距離。

下面我們來推導一下,使用基於視線方向水體的厚度來作為深度因子的公式。

許多朋友第一反應是解直線方程,但用空間向量的特性來求解會更容易。

為方便對照理解,再貼一次上面的圖:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


設觀察方向為 viewDir,厚度為 depth 則有:

P1 + viewDir * depth = P2

分拆為分量運算可得:

P1.x + viewDir.x * depth = P2.x

P1.y + viewDir.y * depth = P2.y

P1.z + viewDir.z * depth = P2.z

可推匯出:

depth = (P2.y - P1.y) / viewDir.y

由此可得如下計算公式:

vec3 viewDir = normalize(v_position.xyz - cc_cameraPos.xyz);

float depth = (v_position.y - g_waterLevel) / viewDir.y

depth = clamp(depth * depthScale,0.0,1.0);

比起直接使用水體深度來說,多了一次求 viewDir 單位向量的運算,以及一次除以 viewDir.y 運算。

在非極端情況下,多出的這一點純邏輯運算在 GPU 上是可以忽略不計的,可以放心使用。

將上述公式新增到渲染物件的 Shader 中,並在折射渲染階段啟用,將結果存入 Alpha 通道即可。

專案中的 Shader 程式碼如下圖所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


最終得到的深度資訊如下:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


深度混合

有了上面的深度資訊,我們只需要在計算出折射顏色後,再用深度資訊與水底顏色混合即可。Shader 程式碼如下圖所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


由於水體的可見度是非線性的,所以對 diffDepth 使用了 pow 函式,這個 power 引數預設是 2.0。

最終可以得到如下效果:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


六、水岸柔邊

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


當我們把攝像機拉近,觀察水面與物體交接處的時候,可以明顯看到一條清晰的邊界。

這條邊界在反射越強的時候越明顯,使我們的水面效果大打折扣。

好在我們已經有了深度資訊,可以根據深度來判斷出哪裡靠近岸邊,並修改菲涅爾因子,使反射越靠近岸邊的時候越弱即可。

核心程式碼如下:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


最終可以實現在全反射的情況下,水面與岸邊依然平滑過渡。效果如下圖所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


再來一張遠視角的圖:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


七、動態天空盒

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


為了增強氛圍感,DEMO 中使用了動態天空盒。

這是一個特別簡單的高效的動態天空盒方案,僅使用了一個雙層紋理混合的半球模型,調節兩張紋理的水平方向流動速度即可。

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案


八、關於DEMO

所有效果引數均可調節,如下圖所示:

再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案



來源:COCOS
原文:https://mp.weixin.qq.com/s/yLEt3yHBj9lBNWm_xANvJw


相關文章