深度测试在模板测试之后,透明度混合之前,如果开启了深度测试,GPU会把该片元的深度值和已经存在于深度缓冲区(Depth Buffer 或者叫 Z-Buffer)中的深度值进行比较,这个比较函数可由开发者设置的,例如片元的深度值大于缓冲区深度值时舍弃该片元
从逻辑上理解
if(ZWrite On && (currentDepthValue ComparisonFunction DepthBufferValue))
写入深度
else
丢弃像素
if(currentDepthValue ComparisonFunction DepthBufferValue)
写入颜色缓冲
else
不写入颜色缓冲
深度缓冲中,对于每一个像素都存储一个深度值,注意这个深度值是非线性的,近处的精度高,远处的精度低。ZTest分为通过和不通过两种情况,ZWrite分为开启和关闭两种情况,一共四种情况
深度测试的比较方法 ComparisonFunction ,和模板测试差不多
比较方法 | 描述 |
---|---|
Greater | 深度大于当前缓冲则通过 |
GEqual | 深度大于等于当前缓存则通过 |
Less | 深度小于当前缓存则通过 |
LEqual | 深度小于等于当前缓存则通过 |
Equal | 深度等于当前缓存则通过 |
NotEqual | 深度不等于当前缓存则通过 |
Always | 深度不论如何都通过 |
Never | 深度不论如何都不通过 |
默认是ZWrite On和ZTest LEqual
下面几种情况显示,ZTest,ZWrite和渲染队列如何影响渲染,蓝色立方体离摄像机最近,红色离的最远
这是默认设置,从近到远,先渲染蓝色,深度缓冲区默认是无穷大,蓝色深度小于无穷大,深度测试通过,写入深度,写入颜色缓冲,再渲染绿色,在蓝色和绿色相交的部分,绿色的深度大于蓝色,深度测试不通过,其他部分正常写入深度,最后渲染红色同理
渲染蓝色,深度测试通过,但是它关闭的深度写入,不写入深度值,写入颜色缓冲,渲染绿色,深度测试通过,写入深度值和颜色,替换掉之前的蓝色
渲染蓝色,和第一张图情况一样,绿色的ZTest设置为Always,不管深度值如何都通过深度测试,写入深度和颜色,重叠部分覆盖蓝色深度值
红色ZTest也改成Always,原理和绿色覆盖蓝色是一样的
绿色的渲染队列调整为Geometry+1,越大越后渲染,渲染顺序是:蓝,红,绿,先按照渲染队列排序,同一个队列,不透明物体按照从近到远排列
绿色ZTest选择Greater,表示当前深度大于深度缓冲区深度才能通过,蓝绿重叠部分,绿色深度大于蓝色深度,深度测试通过,其他部分绿色深度小于无穷大,深度测试不通过
可以使用下面的Shader验证
Shader "MyCustom/ZTestZWrite"
{
Properties
{
_Color ("_Color", Color) = (1, 0, 0, 1)
//自定义枚举
[Enum(Off, 0, On, 1)]_ZWriteMode ("ZWriteMode", Float) = 1
[Enum(UnityEngine.Rendering.CompareFunction)]_ZTestComp ("ZTestComp", Int) = 4
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
ZWrite [_ZWriteMode]
ZTest [_ZTestComp]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
float4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
使用深度测试,实现角色在墙后显示描边的效果,这里用2个Pass实现
第一个Pass渲染描边,角色的深度大于墙的深度,为了让角色通过深度测试需要设置 ZTest Greater,并且需要保持正常的遮挡关系,所以渲染描边时不能写入深度,实现这种半透明效果还要设置混合因子
第二个Pass就是正常的渲染
Shader "MyCustom/XRay"
{
Properties
{
_MainTex("Base 2D", 2D) = "white" {}
_XRayColor ("XRay Color", Color) = (1, 0, 0, 1)
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
float4 _XRayColor;
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : NORMAL;
float3 viewDir : TEXCOORD0;
fixed4 color : COLOR;
};
v2f vertXRay(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//模型顶点到相机方向(模型空间)
o.viewDir = ObjSpaceViewDir(v.vertex);
o.normal = v.normal;
float3 normal = normalize(v.normal);
float3 viewDir = normalize(o.viewDir);
float rim = 1 - dot(normal, viewDir);
o.color = _XRayColor * rim;
return o;
}
fixed4 fragXRay(v2f i) : SV_Target
{
return i.color;
}
sampler2D _MainTex;
float4 _MainTex_ST;
struct v2f2
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f2 vertNormal(appdata_base v)
{
v2f2 o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 fragNormal(v2f2 i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
//XRay
Pass
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
ZWrite Off
ZTest Greater
Blend SrcAlpha One
CGPROGRAM
#pragma vertex vertXRay
#pragma fragment fragXRay
ENDCG
}
//正常绘制
Pass
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
ZWrite On
ZTest LEqual
CGPROGRAM
#pragma vertex vertNormal
#pragma fragment fragNormal
ENDCG
}
}
}
对于多Pass渲染,Unity会从多个Pass里选择渲染队列最靠前的,把物体放在这个队列渲染,这里第一个Pass是Transparent,第二个Pass是Geometry,Geometry靠前渲染就放在了Geometry队列,然后会根据Pass顺序执行
【技术美术百人计划】图形 3.1 深度与模板测试 传送门效果示例