Unity Water Shader


上圖是一個物體浸入水中的效果

原理

  我們使用相機渲染的整個場景的深度圖減去需要忽略的模型的深度,這里忽略的是圖中藍色部分,就保留了其他的深度值。

 

 

  用到Main Camera渲染的深度貼圖: sampler2D _CameraDepthTexture; //在shader中聲明,需要設置相機開啟渲染深度圖;深度圖中記錄的深度值:深度紋理中每個像素所記錄的深度值是從0 到1 非線性分布的。精度通常是 24 位或16 位,這主要取決於所使用的深度緩沖區。當讀取深度紋理時,我們可以得到一個0-1范圍內的高精度值。如果你需要獲取到達相機的距離或者其他線性關系的值,那么你需要手動計算它。

  在頂點着色器中獲取頂點在屏幕空間的位置,用做采樣深度圖的uv坐標:

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.scrPos = ComputeScreenPos(o.pos);//將返回片段着色器的屏幕位置
                COMPUTE_EYEDEPTH(o.scrPos.z);//計算頂點攝像機空間的深度:距離裁剪平面的距離,線性變化;

 

  在像素着色器中采樣Main Camera渲染的深度貼圖(攝像機能看到的整個場景的深度圖):

  

          //計算當前像素深度
                float  depth= tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)).r;//深度值 [0,1]
                depth = LinearEyeDepth(depth);//深度根據相機的裁剪范圍的值[0.3,1000],是將經過透視投影變換的深度值還原了

 

  將此圖的值構建出灰度圖,輸出查看深度圖效果: return float4(depth-999,depth-999,depth-999,1); 可以看出深度圖中,沒有被遮擋的部分並沒有表現出線性漸變,說明區域的值被默認填充為最大的深度,我並沒有找到相關的文件說明,有朋友找到,麻煩私信下。

 

  現在我們測試下頂點在屏幕空間的深度值(僅僅是計算需要計算的模型頂點的深度,與攝像機的深度圖有區別),該值被頂點着色器記錄在o.srcPos.z中,將值在像素着色器中輸出,查看效果: return float4(i.scrPos.z,i.scrPos.z,i.scrPos.z,1); 

  (上圖)想象沿着攝像機的方向,該值呈現出線性分布,說明是該點到近裁減面(以近裁剪面)的距離,假如攝像機裁剪空間是[0.3,10],那么該值在大於等於1的時候呈現出白色,在[0,1]區間內是灰度圖。

   我們將頂點的深度值作為通道輸出: return float4(i.scrPos.z,i.scrPos.z,i.scrPos.z,i.scrPos.z); 

Tags {"Queue" = "Transparent"}
Blend SrcAlpha OneMinusSrcAlpha 

這個樣子,我們就實現根據攝像機與頂點之間的深度值變化,實現與視角有關的透明效果,深度值在[0,1]范圍內,則會透明,大於1則會呈現出材質顏色;

 

現在我們將我們轉化過的深度圖深度值(線性分布)與頂點深度相減: depth -= i.scrPos.z; 

 

為什么其他地方白色相減不等於黑色? 

  個人認為:在深度圖中,沒有被遮擋的地方被默認填充為最大深度1,轉化為距離相機的線性距離時,也被轉化為最大距離,例1000。這個值再減去模型的深度時,結果遠大於模型深度,用顏色顯示出來,即為白色。

 

現在將深度值輸出到像素着色器的rgba通道:查看效果: return float4(depth,depth,depth, depth); 與模型相交部分變成了透明。

 

接下來,我們使用變量ColorDepth控制深度值,並增加2個顏色,來表現深度的范圍:

    Properties
    {    _Color0("Water Color",Color) = (1,1,1,1)//水的顏色
        _Color1("Water Depth",Color) = (0,0,0,0)//水的深度的顏色
        _Alpha("Alpha",Range( 0,1))= 1//水面的正題透明度
        _ColorDepth("ColorDepth",Range( 0,1))= 0//水的深度
    }
                //計算水的透明度: 使用深度值
                float alpha = saturate( _Alpha*depth);

                //計算顏色深度:
                float colDepth = saturate(_ColorDepth*depth);
                colDepth = 1-colDepth;
                colDepth = lerp(colDepth, colDepth*colDepth*colDepth, 0.5);//調整深度,看個人喜好

                half3 col;
                col.rgb = lerp(_Color0.rgb, _Color1.rgb, colDepth);

                return float4(col.rgb, alpha );

 

在物體遮擋處,藍色越深,表示越深,天藍色,是海水顏色。現在除了物體遮擋處的深度表現正確,其他地方審定其實不正確。我們在水下加一塊地皮:

是不是有感覺了,我們再調節下顏色等參數:

源代碼:

Shader "JQM/DepthTest_1"
{
    Properties
    {    _Color0("Water Color",Color) = (1,1,1,1)//水的顏色
        _Color1("Water Depth",Color) = (0,0,0,0)//水的深度的顏色
        _Alpha("Alpha",Range( 0,1))= 1//水面的正題透明度
        _ColorDepth("ColorDepth",Range( 0,1))= 0//水的深度
    }
    SubShader
    {
        Tags {"Queue" = "Transparent"}

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct VertexOutput
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float4 scrPos : TEXCOORD1;
            };


            float4 _Color0;
            float4 _Color1;
            float _Alpha;//水的透明度
            float _ColorDepth;

            sampler2D _CameraDepthTexture;
            
            VertexOutput vert (appdata v)
            {
                VertexOutput o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.scrPos = ComputeScreenPos(o.pos);//將返回片段着色器的屏幕位置
                COMPUTE_EYEDEPTH(o.scrPos.z);//計算頂點攝像機空間的深度:距離裁剪平面的距離
                return o;
            }
            
            fixed4 frag (VertexOutput i) : COLOR
            {
                //計算當前像素深度
                float  depth= tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)).r;//UNITY_PROJ_COORD:深度值 [0,1]
                depth = LinearEyeDepth(depth);//深度根據相機的裁剪范圍的值[0.3,1000],是將經過透視投影變換的深度值還原了
                depth -= i.scrPos.z;
                //計算水的透明度: 使用深度值
                float alpha = saturate( _Alpha*depth);

                //計算顏色深度:
                float colDepth = saturate(_ColorDepth*depth);
                colDepth = 1-colDepth;
                colDepth = lerp(colDepth, colDepth*colDepth*colDepth, 0.5);//調整深度,看個人喜好

                half3 col;
                col.rgb = lerp(_Color0.rgb, _Color1.rgb, colDepth);

                return float4(col.rgb, alpha );  
            }
            ENDCG
        }
    }
}

 

現在基本完成了物體浸入水中的效果:

接下來,我還需要什么效果呢?

1. UV偏移的動態水面;

2. 反射;

3. 折射;

 

 

關於相機透明度問題

深度增加,透明度降低。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM