前言
这篇文章开始写的时候还是国庆的第一天,一直拖到现在才写完……
最近工作变动,我又有了相对更多时间来研究渲染相关的东西了,还是挺爽的。之前初步研究了一下深度图,利用深度图可以做到很多有意思的效果,这里简单讲一下怎么用深度图实现地形扫描和简单的水面渲染效果。
什么是深度图
深度图是一张灰度图,其每个像素的明度值表示的是该像素到摄像机的距离。
想要在Unity中获取深度图,需要如下操作:
- 对Camera进行设置,让其生成深度贴图(默认是不生成的)
这一步需要C#脚本,随便创建一个脚本并编写以下代码,这句代码只要执行过一次,摄像机就会一直保存这个配置。
Camera.main.depthTextureMode |= DepthTextureMode.Depth;
如果操作正确,Camera的Inspector下面会出现以下的提示。
- 在Shader中获取深度图数据
直接在CGPROGRAM中申明约定名称的变量,Unity会自动将相机生成的深度图数据填充到这个变量中。
sampler2D _CameraDepthTexture;
地形扫描效果
有了深度图就很容易实现地形扫描效果。使用后处理实现,将某个范围的深度值区域进行高亮的着色后以滤色混合到画面上,然后脚本控制深度参数从近到远变化,效果如下:
完整Shader代码:(后处理Shader,需要用后处理脚本挂载到摄像机上才有用)
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; //如果不想使用脚本控制动画,也可以直接在Shader里实现动画
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
}
}
}
水面渲染和能量罩
守望先锋温斯顿的护罩:
可以看到护罩的网格与其他物体相交的地方会有高亮渐变。就是用深度图实现的——利用透明物体不进行深度写入的特性,在每个片元中将当前片元的深度和屏幕深度(屏幕深度中没有透明物体的深度信息)对比,就能计算出当前物体和其他环境物体相交的程度。
不过温斯顿护罩别人也做过很多了,所以这里我就做了水面的效果,原理上一样的——通过判断深度差异计算出水面的深浅,并体现在水面的颜色、透明度以及和地形交界处的白色水花效果上:
除了上面说的三项基于深度图实现的特性,还实现了法线、光滑度(基于StandardPBR)、顶点运动(简单的Sin曲线)。
最终呈现的效果如下:(波光粼粼的感觉是后处理Bloom的功劳)
其实这个水面效果近看就会发现很扯淡,非常假。毕竟水面渲染还有很多学问可以做,这里主要是展示一下深度图的应用而已。
完整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等效果,不过因为有别的需求来了,暂时没有继续研究下去。感兴趣的可以自己搜索一下,深度图的相关内容还是非常多的。
参考
神奇的深度图:复杂的效果,不复杂的原理
Unity3D中的深度纹理和法线纹理
Unity Shader-深度图基础及应用
Unity Shader-深度相关知识总结与效果实现