潮溼效果之水紋

水紋

疫情肆掠,在家坐月子坐到傻了,開始寫點文章找回狀態。git

本文是關於咱們遊戲 水紋 效果的實現細節。github

這裏的 水紋 效果是 天氣系統 的一部分,主要表現 雨水流動 的效果,以下圖:算法

本文會先介紹一下咱們的實現方式,再對比一下其餘實現。bash

咱們的實現

常見的水波紋效果,通常都是依靠 法線貼圖 + UV動畫 來完成的。函數

考慮到 水平方向垂直方向 水的流動方式不同,咱們提供了兩套水紋貼圖。動畫

水平方向的法帖以下:ui

垂直方向的法帖以下:spa

針對 水平的地表垂直的牆壁 咱們會選擇不一樣的法帖。插件

除去 水平法帖垂直法帖 外,咱們還提供了以下水紋參數:code

Wet Bump Speed HWet Bump Spped V 分別表明 水平流速垂直流速,這裏的流速指是 UV動畫 的速度。

只有流速還不夠,咱們還須要 流動方向

  • 對於 垂直流動,其 UV動畫的方向 永遠是 (0, 1),這和 垂直法帖 是一致的。

  • 對於 水平流動,咱們固定死了方向爲 (0.5, 0.5),固然這裏也能夠開放出來給美術調整。

最終的水流方向是 水平方向垂直方向 插值而成,權重由 法線的傾斜度 決定。

此外,爲了保證水紋的連續性,場景中的渲染單元經過其 世界座標的xz份量 來映射 水紋貼圖 的紋理座標,Wet Bump World Scale 經過對 座標的縮放 來控制 波紋的大小

下圖是不一樣波紋大小的對比:

最後,咱們還能夠經過調整 Wet Bump Height Scale 來調整 波紋的強弱Wet Bump Height Scale 的做用相似於 UnpackScaleNormalBumpScale 參數。

好了,實現細節講的差很少了,下面貼代碼:

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 函數,傳入 WorldNormalWorldPosition.xz 便可。

下圖是替換 楚留香 的水紋算法後,咱們房頂的水流效果:

其餘參考

最後貼一個Unity插件,咱們的實現,當初也參考了這個插件: wet-animation-shaders,截圖以下:

原理大同小異,就不羅嗦了。

我的主頁

本文的我的主頁連接:baddogzz.github.io/2020/02/05/…

好了,拜拜。

相關文章
相關標籤/搜索