疫情肆掠,在家坐月子坐到傻了,開始寫點文章找回狀態。git
本文是關於咱們遊戲 水紋 效果的實現細節。github
這裏的 水紋 效果是 天氣系統 的一部分,主要表現 雨水流動 的效果,以下圖:算法
本文會先介紹一下咱們的實現方式,再對比一下其餘實現。bash
常見的水波紋效果,通常都是依靠 法線貼圖 + UV動畫 來完成的。函數
考慮到 水平方向 和 垂直方向 水的流動方式不同,咱們提供了兩套水紋貼圖。動畫
水平方向的法帖以下:ui
垂直方向的法帖以下:spa
針對 水平的地表 和 垂直的牆壁 咱們會選擇不一樣的法帖。插件
除去 水平法帖 和 垂直法帖 外,咱們還提供了以下水紋參數:code
Wet Bump Speed H 和 Wet Bump Spped V 分別表明 水平流速 和 垂直流速,這裏的流速指是 UV動畫 的速度。
只有流速還不夠,咱們還須要 流動方向:
對於 垂直流動,其 UV動畫的方向 永遠是 (0, 1),這和 垂直法帖 是一致的。
對於 水平流動,咱們固定死了方向爲 (0.5, 0.5),固然這裏也能夠開放出來給美術調整。
最終的水流方向是 水平方向 和 垂直方向 插值而成,權重由 法線的傾斜度 決定。
此外,爲了保證水紋的連續性,場景中的渲染單元經過其 世界座標的xz份量 來映射 水紋貼圖 的紋理座標,Wet Bump World Scale 經過對 座標的縮放 來控制 波紋的大小。
下圖是不一樣波紋大小的對比:
最後,咱們還能夠經過調整 Wet Bump Height Scale 來調整 波紋的強弱,Wet Bump Height Scale 的做用相似於 UnpackScaleNormal 的 BumpScale 參數。
好了,實現細節講的差很少了,下面貼代碼:
half3 SGameWetBump(half2 wetFactors, float3 worldPos, half3 worldNormal)
{
half worldNormalY = worldNormal.y;
float isVertical = (worldNormalY * worldNormalY) < 0.9;
float flowTime = _Time.y;
//豎直方向
float2 flowDir1 = float2(0, 1) * _WetBumpSpeedV;
float uvX1 = lerp(worldPos.x, worldPos.z, abs(worldNormal.x) > abs(worldNormal.z));
float uvY1 = worldPos.y;
float2 uv1 = float2(uvX1, uvY1);
float2 flowUV1 = float2(uv1 + flowTime * flowDir1) * _WetBumpWorldScale;
//xz平面方向
float2 flowDir2 = float2(0.5,0.5) * _WetBumpSpeedH;
float uvX2 = worldPos.x;
float uvY2 = worldPos.z;
float2 uv2 = float2(uvX2, uvY2);
float2 flowUV2 = float2(uv2 + flowTime * flowDir2) * _WetBumpWorldScale;
//根據是否垂直方向插值
float2 flowUV = lerp(flowUV1, flowUV2, 1 - isVertical);
#if defined(SGAME_WET_BUMP_MAP_H)
half3 flowBump = UnpackNormal(tex2D(_WetBumpMapH, flowUV));
#else
half3 flowBump = UnpackNormal(tex2D(_WetBumpMapV, flowUV));
#endif
half3 bumpNormal = lerp(half3(0,0,1), flowBump, wetFactors.x);
return bumpNormal;
}
複製代碼
注意,SGameWetBump 的返回值是 切線空間 下波紋的 法線偏移,咱們把 法線偏移 和 原切線空間法線 相加,再轉到 世界空間 來計算光照,就能夠表現出 水紋 的流動。
// 潮溼法線的計算
float3 wetNormal.xy = tangentNormal + wetBumpNormal.xy * _WetBumpHeightScale;
wetNormal.z = sqrt(1.0 - saturate(dot(wetNormal.xy, wetNormal.xy)));
複製代碼
咱們的作法就介紹到這裏,下面來看一下 楚留香 的作法。
經過分析代碼,我發現 楚留香 的 水紋 並不依賴 法線貼圖,而是經過 噪聲 生成。
這裏的 噪聲算法 留待後續研究,先附上代碼:
float Hash(in float2 p)
{
return frac(((sin(dot(p,float2(127.1,311.7))))*(43758.5453)));
}
float Noise(float2 p)
{
float2 i = floor(p);
float2 f = frac(p);
float2 u = (f * f) * (3.0 - 2.0 * f);
return -1.0 + (2.0 * lerp(lerp(Hash(i + float2(0.0,0.0)), Hash(i + float2(1.0,0.0)),u.x), lerp(Hash(i + float2(0.0,1.0)), Hash(i+ float2(1.0,1.0)),u.x),u.y));
}
float SeaOctave(float2 uv)
{
uv += Noise(uv);
float2 wv = 1.0 - abs(sin(uv));
float2 swv = abs(cos(uv));
wv = lerp(wv,swv,wv);
return 1.0 - pow(wv.x * wv.y, 0.65);
}
float3 RippleNormal(in float3 N,in float2 uv)
{
float4 jitterUV;
half worldscale = 5;
worldscale = 1;
jitterUV = uv.xyxy * float4(1.5,5,5,1.5) * worldscale;
// 這裏的CameraPosPS.w我用_Time.y代替,就有水流運動了,你們能夠自行調節速度。
//float4 seed = clamp(N.xzxz * 10000,-1,1) * float4(20,20,6,6) * CameraPosPS.w;
float4 seed = clamp(N.xzxz * 10000,-1,1) * float4(20,20,6,6) * _Time.y;
float R1 = SeaOctave(jitterUV.yx*10 - seed.x) + SeaOctave(float2(jitterUV.z * 3 - seed.z, jitterUV.w * 3));
float R3 = SeaOctave(float2(jitterUV.xy*4 - seed.w)) + SeaOctave(jitterUV.zw * 8 - seed.y);
R3 *= 0.5;
float R_D = (R1 * N.x * N.x + R3 * N.z * N.z)* 5 + (R1+R3) * 0.1 - 0.212;
// 這裏的 EnvInfo.x 能夠控制水紋的強度
float EnvInfo = 0.6;
R_D *= (step(0.5,EnvInfo.x) * EnvInfo.x * 1.3);
return normalize(lerp((N + float3(0,0,R_D)),N,(1 - 0.2 * saturate(N.y))));
}
複製代碼
上述代碼我已經改到能夠在Unity下運行了,若是須要水流效果,直接調用 RippleNormal 函數,傳入 WorldNormal 和 WorldPosition.xz 便可。
下圖是替換 楚留香 的水紋算法後,咱們房頂的水流效果:
最後貼一個Unity插件,咱們的實現,當初也參考了這個插件: wet-animation-shaders,截圖以下:
原理大同小異,就不羅嗦了。
本文的我的主頁連接:baddogzz.github.io/2020/02/05/…。
好了,拜拜。