再加億點點細節!Cocos 基於平面著色的 3D 水面渲染方案
但該專案基於 Cocos Creator 延遲渲染管線,對專案和裝置要求較高,所以麒麟子專門準備了這個獨立的水面效果分享,希望能夠對大家有所幫助。
二、水面渲染流程
水面渲染技術非常多,不同段位的產品,對水面的要求不同。
毛星雲的《真實感水體渲染技術總結》這篇文章中,通過對一些 3A 大作的水面渲染進行分析,列出了非常多的技術要點,有興趣的朋友可以拜讀。
水面渲染技術從簡單到複雜來排序,可以分為以下三類:
平面著色
頂點動畫
流體模擬
本文實現的是基於平面著色的水面效果,雖然它並非高階效果,但卻是大部分 3D 專案中採用的方案。
基於平面著色的水面渲染主要涉及以下幾個部分:
反射
折射
水深效果
水岸柔邊
動態天空盒
法線圖與光照
岸邊浪花
由於時間關係,法線圖與光照與岸邊浪花暫未實現。
標準的渲染流程如下所示:
可以看出,如果要實現所有效果,至少需要繪製場景4次。
由於這裡的深度圖只是和折射搭配使用,8位精度足夠用了,我們可以考慮借用折射圖中的 Alpha 通道來儲存深度資訊。
優化後的流程圖如下:
三、反射貼圖渲染
麒麟子在《用實時反射 Shader 增強畫面顏值》中已經完整地剖析了實時反射相關原理,在此就不再敷述,有需要了解的讀者可直接點選檢視。
這裡主要講一講本 DEMO 中的實現步驟。
步驟1:使用程式碼新建一個 RenderTexture。
步驟2:建立一個節點,新增攝像機元件,並將 clearFlags、clearColor、visibility 屬性與主攝像機同步。
步驟3:設定反射攝像機的渲染優先順序,確保比主攝像機先渲染。
步驟4:將新建立的 RenderTexture 賦值給此攝像機的 targetTexture 屬性。
以上步驟的程式碼在 WaterPlane.ts 中,如下圖所示:
步驟5:在 lateUpdate 中同步主攝像機引數。
步驟6:在 lateUpdate 中根據實時反射原理,動態計算攝像機關於主攝像機的映象位置和旋轉。
最終,渲染得到的 RenderTexture 如下:
麒麟小貼士:
所有物體的材質,需要加入自定義裁剪面,裁剪掉水面以下的部分。
可以明顯看到,上圖中綠色物體的倒影,水面以下的部分是被裁剪掉了的。
四、折射貼圖渲染
折射渲染的原理非常簡單:
渲染水平面以下的部分到 RenderTexture
在水面渲染階段使用噪聲圖進行擾動,以模擬出水面折射效果
折射渲染的流程與反射渲染大致相同,只有兩個細小的差別:
用於折射渲染的攝像機所有引數均與主攝像保持一致即可
折射渲染階段,物體被裁剪掉的是水面以上的部分
下面我們來看看,本 DEMO 中關於折射的實現步驟。
步驟1:使用程式碼新建一個 RenderTexture。
步驟2:建立一個節點,新增攝像機元件,並將 clearFlags、clearColor、visibility 屬性與主攝像機同步。
步驟3:設定反射攝像機的渲染優先順序,確保比主攝像機先渲染。
步驟4:將新建立的 RenderTexture 賦值給此攝像機的 targetTexture 屬性。
以上步驟的程式碼在 WaterPlane.ts 中,如下圖所示:
麒麟小貼士: 注意紅色線框部分,本 DEMO 中折射貼圖的 Alpha 通道用於標記深度資訊,所以需要確保 Alpha 通道的值為 255。
步驟5:在 lateUpdate 中同步主攝像機引數、位置、旋轉等資訊。
最終,渲染得到的 RenderTexture 如下:
五、水面渲染
水面渲染主要利用了投影紋理技術,將頂點的投影座標轉化為 UV,對摺射和反射貼圖進行取樣。
由於使用了折射貼圖,我們的水面材質不需要開啟 Alpha 混合。
折射渲染
步驟1:根據投影座標計算出螢幕 UV。如下所示:
vec2 screenUV = v_screenPos.xy / v_screenPos.w * 0.5 + 0.5;
步驟2:取樣折射貼圖,可以得到如下渲染效果:
左邊為正常渲染效果,右邊為標記了折射內容的效果
步驟3:使用噪聲圖對摺射進行擾動,可得到如下效果:
反射渲染
步驟1:與折射渲染一樣,根據投影座標計算出螢幕 UV。
步驟2:取樣反射貼圖,可以得到如下渲染效果:
步驟3:使用噪聲圖對反射進行擾動,可得到如下效果:
菲涅爾混合
菲涅爾的計算公式從玉兔的邊緣光教程開始,到實時反射等場合,已經出現過很多次了。下面是核心程式碼:
折射可以視為水體本色,利用菲涅爾因子與反射內容混合,即可實現一個帶折射和反射的水體效果。
虛擬碼如下:
finalColor = mix(refractionColor,reflectionColor,fresnel)
最終可以得到如下顯示效果:
完整程式碼程式碼請檢視專案中的 effect-water.effect 檔案。
六、水深效果
從上面的動畫中可以看出,雖然折射和反射效果都有了。但畫風有些奇怪,完全沒有水面的感覺。
這是水面沒有深淺效果導致的。
我們來看看,如何獲取深度資訊,並根據深度資訊實現水深效果。
獲取深度資訊
從上圖中,我們可以清晰地看到,靠近岸邊的海水的顏色比遠處海水的顏色透明得多。
產生這種現象的主要原因,就是基於視線方向的水體厚度不同。
什麼叫基於視線方向的水體厚度,請看下圖:
我們通常說的水體深度,是指在忽略視線因素的情況,水面到水底的高度差。
在不追究細節的情況下,我們可以簡單地使用高度差來作為水的深度。
一種可能的虛擬碼如下:
depth = clamp((g_waterLevel - v_position.y) * depthScale,0.0,1.0);
其中 depthScale 是我們的深度縮放因子,可以用來調節比例尺問題,以及水體能見度線性衰減速率。
而基於視線方向的水體厚度,是指視線方向與水平面和水底交點的距離差。即圖中 點 P1 到 點 P2 的距離。
下面我們來推導一下,使用基於視線方向水體的厚度來作為深度因子的公式。
許多朋友第一反應是解直線方程,但用空間向量的特性來求解會更容易。
為方便對照理解,再貼一次上面的圖:
設觀察方向為 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 程式碼如下圖所示:
最終得到的深度資訊如下:
深度混合
有了上面的深度資訊,我們只需要在計算出折射顏色後,再用深度資訊與水底顏色混合即可。Shader 程式碼如下圖所示:
由於水體的可見度是非線性的,所以對 diffDepth 使用了 pow 函式,這個 power 引數預設是 2.0。
最終可以得到如下效果:
六、水岸柔邊
當我們把攝像機拉近,觀察水面與物體交接處的時候,可以明顯看到一條清晰的邊界。
這條邊界在反射越強的時候越明顯,使我們的水面效果大打折扣。
好在我們已經有了深度資訊,可以根據深度來判斷出哪裡靠近岸邊,並修改菲涅爾因子,使反射越靠近岸邊的時候越弱即可。
核心程式碼如下:
最終可以實現在全反射的情況下,水面與岸邊依然平滑過渡。效果如下圖所示:
再來一張遠視角的圖:
七、動態天空盒
為了增強氛圍感,DEMO 中使用了動態天空盒。
這是一個特別簡單的高效的動態天空盒方案,僅使用了一個雙層紋理混合的半球模型,調節兩張紋理的水平方向流動速度即可。
八、關於DEMO
所有效果引數均可調節,如下圖所示:
來源:COCOS
原文:https://mp.weixin.qq.com/s/yLEt3yHBj9lBNWm_xANvJw
相關文章
- 基於React的SSG靜態站點渲染方案React
- 免費開源!細節拉滿的一個2D實時水面反射效果Demo,基於Cocos Creator 3.5反射
- css細節知識點CSS
- TCP 中的兩個細節點TCP
- 什麼是實時渲染,3D實時渲染的優缺點3D
- 億點細節!《黑神話:悟空》的中國古建築震撼全世界!
- Golang 基於單節點 Redis 實現的分散式鎖GolangRedis分散式
- 基於minikube快速搭建kubernetes單節點環境
- React從零實現-節點建立和渲染React
- Oracle細節及難點總結Oracle
- KDTree求平面最近點對
- 渲染中的光照著色方式:PBR(Physically Based Rendering,物理基礎渲染)與 傳統經驗渲染
- OpenGL 和 GLSL 在頂點著色器中動態調整裁剪平面引數的簡單程式碼示例
- 基於 SSR 的預渲染首屏直出方案
- 基於etcd的選主功能實現的主備節點管理
- 基於Vue的點對點聊天專案Vue
- 浮點數的加減乘除運算細節
- Android 專案中對於記憶體優化的幾個細節點Android記憶體優化
- JavaScript學習之DOM(節點、節點層級、節點操作)JavaScript
- 基於KubeEdge的邊緣節點分組管理設計與實現
- 【技術向】基於jarm的Tor中繼節點遠端識別JAR中繼
- 節點快取的優缺點快取
- 關於python類屬性和例項屬性的一些細節注意點Python
- 計算幾何——平面最近點對
- dom4j 根據xml節點路徑查詢節點,找到對應的目標節點下的子節點,對節點Text值進行修改XML
- 基於webrtc實現點對點桌面分享Web
- MYSQL索引建立需要注意以下幾點細節MySql索引
- 搭建Solana驗證者節點(全節點)的過程
- 【窮舉】Max Points on a Line平面上共線的點
- 資料庫——查詢樹形結構某節點的所有子節點、所有父節點資料庫
- consul 多節點/單節點叢集搭建
- 點點動畫~畫出懂你的3D魔方動畫3D
- Kubernetes – 節點
- 基於 GraphQL 實踐的一點思考
- JQuery2:節點選取與節點插入jQuery
- DataNode工作機制 & 新增節點 &下線節點
- mysql根據節點查詢所有葉節點MySql
- rac新增節點前之清除節點資訊