前言
這篇文章開始寫的時(shí)候還是國(guó)慶的第一天,一直拖到現(xiàn)在才寫完……

最近工作變動(dòng),我又有了相對(duì)更多時(shí)間來研究渲染相關(guān)的東西了,還是挺爽的。之前初步研究了一下深度圖,利用深度圖可以做到很多有意思的效果,這里簡(jiǎn)單講一下怎么用深度圖實(shí)現(xiàn)地形掃描和簡(jiǎn)單的水面渲染效果。
什么是深度圖
深度圖是一張灰度圖,其每個(gè)像素的明度值表示的是該像素到攝像機(jī)的距離。

想要在Unity中獲取深度圖,需要如下操作:
-
對(duì)Camera進(jìn)行設(shè)置,讓其生成深度貼圖(默認(rèn)是不生成的)
這一步需要C#腳本,隨便創(chuàng)建一個(gè)腳本并編寫以下代碼,這句代碼只要執(zhí)行過一次,攝像機(jī)就會(huì)一直保存這個(gè)配置。
Camera.main.depthTextureMode |= DepthTextureMode.Depth;
??如果操作正確,Camera的Inspector下面會(huì)出現(xiàn)以下的提示。

-
在Shader中獲取深度圖數(shù)據(jù)
直接在CGPROGRAM中申明約定名稱的變量,Unity會(huì)自動(dòng)將相機(jī)生成的深度圖數(shù)據(jù)填充到這個(gè)變量中。
sampler2D _CameraDepthTexture;
地形掃描效果
有了深度圖就很容易實(shí)現(xiàn)地形掃描效果。使用后處理實(shí)現(xiàn),將某個(gè)范圍的深度值區(qū)域進(jìn)行高亮的著色后以濾色混合到畫面上,然后腳本控制深度參數(shù)從近到遠(yuǎn)變化,效果如下:

完整Shader代碼:(后處理Shader,需要用后處理腳本掛載到攝像機(jī)上才有用)
Shader "PostEffect/Scan"
{
Properties
{
[HideInInspector] _MainTex ("Texture", 2D) = "white" {}
[HDR] _ScanColor ("Scan Color", Color) = (0,1,0,1)
_GridTex ("Grid Tex", 2D) = "white" {}
_GridTile ("Grid Tile", float) = 1
_Value ("Value", Range(0,1)) = 1
_Width ("Width", Range(0,1)) = 0.1
_HighlightWidth ("Hightlight Width", Range(0,1)) = 0.01
}
SubShader
{
Tags { "PreviewType" = "Plane" }
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Value;
float _HighlightWidth;
float _Width;
float _GridTile;
float3 _ScanColor;
sampler2D _CameraDepthTexture;
sampler2D _GridTex;
sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : POSITION;
float4 screenPos : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
v2f vert (appdata v)
{
v2f o;
o.uv = v.uv;
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz / 1.0;
return o;
}
float greyscale(float4 color)
{
return (color.r + color.g + color.b) / 3;
}
fixed3 screen(fixed3 color0, fixed3 color1)
{
return 1 - (1 - color0) * (1 - color1);
}
fixed4 frag (v2f i) : SV_Target
{
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));
float linear01Depth = Linear01Depth(depth); //將深度值映射到[0,1]范圍
fixed z = linear01Depth;
half3 color = half3(0,0,0);
half3 scan = _ScanColor;
// _Value = _Time.y / 4 % 1.0; //如果不想使用腳本控制動(dòng)畫,也可以直接在Shader里實(shí)現(xiàn)動(dòng)畫
if(z > _Value - _HighlightWidth && z < _Value)
{
float intensity = greyscale(float4(scan,1));
color = fixed4(intensity,intensity,intensity,1);
}
else if(z > _Value - _Width && z < _Value)
{
color = lerp(fixed3(0,0,0), scan, (z - _Value + _Width) / _Width);
float2 uv = i.worldPos.xy * _GridTile * z;
color *= tex2D(_GridTex, uv);
}
float4 srcColor = tex2D(_MainTex, i.uv);
return float4(screen(color, srcColor.rgb), 0);
}
ENDCG
}
}
}
水面渲染和能量罩
守望先鋒溫斯頓的護(hù)罩:

可以看到護(hù)罩的網(wǎng)格與其他物體相交的地方會(huì)有高亮漸變。就是用深度圖實(shí)現(xiàn)的——利用透明物體不進(jìn)行深度寫入的特性,在每個(gè)片元中將當(dāng)前片元的深度和屏幕深度(屏幕深度中沒有透明物體的深度信息)對(duì)比,就能計(jì)算出當(dāng)前物體和其他環(huán)境物體相交的程度。
不過溫斯頓護(hù)罩別人也做過很多了,所以這里我就做了水面的效果,原理上一樣的——通過判斷深度差異計(jì)算出水面的深淺,并體現(xiàn)在水面的顏色、透明度以及和地形交界處的白色水花效果上:

除了上面說的三項(xiàng)基于深度圖實(shí)現(xiàn)的特性,還實(shí)現(xiàn)了法線、光滑度(基于StandardPBR)、頂點(diǎn)運(yùn)動(dòng)(簡(jiǎn)單的Sin曲線)。
最終呈現(xiàn)的效果如下:(波光粼粼的感覺是后處理Bloom的功勞)

其實(shí)這個(gè)水面效果近看就會(huì)發(fā)現(xiàn)很扯淡,非常假。畢竟水面渲染還有很多學(xué)問可以做,這里主要是展示一下深度圖的應(yīng)用而已。
完整Shader代碼:
Shader "Custom/Water"
{
Properties
{
_Color ("Shallow Color", Color) = (1,1,1,1)
_DeepColor ("Deep Color", Color) = (1,1,1,1)
[HDR] _EdgeColor ("Edge Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
_NormalScale ("Normal Scale", Range(0,5)) = 1.0
_EdgeTex ("Edge", 2D) = "white" {}
_Edge ("Edge Value", Range(0,100)) = 1
_EdgeWidth ("Edge Width",Range(0,1)) = 0.0
_Glossiness ("Smoothness", Range(0,1)) = 0.5
// _Metallic ("Metallic", Range(0,1)) = 0.0
_Value ("Value", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" "ForceNoShadowCasting"="True"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
BlendOp Add
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert alpha
#pragma target 3.5
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
sampler2D _EdgeTex;
sampler2D _NormalMap;
fixed _Value;
half3 _EdgeColor;
half _Glossiness;
half _NormalScale;
// half _Metallic;
fixed4 _Color;
fixed _Edge;
fixed _EdgeWidth;
fixed4 _DeepColor;
struct Input
{
float2 uv_MainTex;
float2 uv_NormalMap;
float2 uv_EdgeTex;
float waveNoise;
float3 worldNormal;
float3 viewDir;
float4 screenPos;
float eyeZ;
float4 test;
};
fixed greyscale(fixed3 input)
{
return (input.r + input.g + input.b) / 3;
}
fixed3 screen(fixed3 color0, fixed3 color1)
{
return 1 - (1 - color0) * (1 - color1);
}
void vert(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
//
COMPUTE_EYEDEPTH(o.eyeZ);
//
v.vertex.y += sin(v.vertex.z / 2 + _Time.y * 2) * 0.5;
v.vertex.y += sin(v.vertex.x / 2 + _Time.y * 2) * 0.5;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
float3 worldNormal = normalize(IN.worldNormal);
float3 viewDir = normalize(IN.viewDir);
float screenZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)));
float intersect = (1.0 - pow((screenZ - IN.eyeZ),0.3));
float v = saturate(intersect);
fixed3 Edge = tex2D(_EdgeTex, IN.uv_EdgeTex + sin(_Time) / 50) * _Edge * _EdgeColor;
Edge = lerp(fixed3(0,0,0),Edge,smoothstep(1 - _EdgeWidth,1,v));
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * lerp(_Color, _DeepColor, saturate(lerp(_Value, 1 - _Value, screenZ - IN.eyeZ)));
o.Albedo = screen(c.rgb, Edge);
o.Alpha = c.a;
half3 normal = UnpackScaleNormal(tex2D(_NormalMap, IN.uv_NormalMap + _Time / 200), _NormalScale);
o.Normal = normal;
o.Metallic = 0;
o.Smoothness = _Glossiness;
}
ENDCG
}
FallBack "Diffuse"
}
除了上面這些,深度圖還可以做景深、霧效、SSAO等效果,不過因?yàn)橛袆e的需求來了,暫時(shí)沒有繼續(xù)研究下去。感興趣的可以自己搜索一下,深度圖的相關(guān)內(nèi)容還是非常多的。
參考
神奇的深度圖:復(fù)雜的效果,不復(fù)雜的原理
Unity3D中的深度紋理和法線紋理
Unity Shader-深度圖基礎(chǔ)及應(yīng)用
Unity Shader-深度相關(guān)知識(shí)總結(jié)與效果實(shí)現(xiàn)