上圖是一個物體浸入水中的效果
原理
我們使用相機渲染的整個場景的深度圖減去需要忽略的模型的深度,這里忽略的是圖中藍色部分,就保留了其他的深度值。
用到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. 折射;
關於相機透明度問題
深度增加,透明度降低。