在其他游戏里见到过这样的效果:
人物走到建筑后面,被建筑挡住的部分做特殊显示、没有被建筑挡住的部分正常渲染。
想要实现这种效果,关键的要点是 ZTest.
今天借助unity了解了 ZTest 机制.再此做一下总结.
创建一个空场景,两个 cube,距离近的是 cube_1, 远的是 cube_2
分别给 cube_1 ,cube_2 写 shader
cube_1 的 shader 叫做 Shader_1. Shader_1 只包含一个颜色属性,把 cube_1 渲染成蓝色
Shader_1 代码如下
Shader "Custom/Shader_1"
{
Properties
{
_Color("color",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
fixed4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
cube_2 的 shader 是 Shader_2 ,Shader_2 的 SubShader 包含 2个Properties 和 2个 pass
第一个 Pass 使用 _Color 正常渲染
第二个 Pass 使用 _SubColor 渲染
Shader "Custom/Shader_2"
{
Properties
{
_Color("color",Color) = (1,1,1,1)
_SubColor("sub color",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
LOD 100
// draw normal
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
fixed4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
// draw hide shadow
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
fixed4 _SubColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _SubColor;
}
ENDCG
}
}
}
给 cube2 的 material 赋值颜色,_Color 赋值为 红色,_SubColor 赋值为 绿色.
此时,场景里面的 cube2 会显示为绿色,因为采用 subcolor 的 pass 写在后面, 第2个 pass 会在前一个 pass 的基础上继续渲染,二者绘制的像素都一样,所以结果看起来是第2个 pass 在起作用
此时,如果二者有所重叠,希望 cube2 的 第2个 pass 起作用,没有重叠的部分 第1个 pass 起作用的话,关键点是在 cube2 的 第2个 pass 上面加上 ZTest Greater.
修改之后 shader_2 代码如下,他跟上面的代码只有 ZTest 不同
Shader "Custom/Shader_2"
{
Properties
{
_Color("color",Color) = (1,1,1,1)
_SubColor("sub color",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
LOD 100
// draw normal
Pass
{
ZTest LEqual
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
fixed4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
// draw hide shadow
Pass
{
ZTest Greater
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
fixed4 _SubColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _SubColor;
}
ENDCG
}
}
}
这样一来, cube_2 被 cube_1 覆盖的部分,会显示 pass2 的绿色 绘制结果, 没有被 cube_1 覆盖的部分,会正常显示。
这样就基本上实现了 我们想要的“被挡住的部分 采用另外一种绘制方式” 的效果。
需要注意的点是,
Unity 在绘制物体时,绘制顺序完全按照 Shader 的 Queue 来绘制的。 Queue 值越小,越先绘制,并且绘制时,有一个 “深度缓冲区” 的概念。因此,想要保证效果正确,必须 cube_1 的 queue 值 <= cube_2 的 queue值。
可以这样理解:
深度缓冲区是一个和屏幕一样大小的白纸,纸上的每个像素都有一个深度值,起初每个像素的深度值都是 max.
绘制了 cube_1 之后, cube_1 的 shader 没有制定 z test 和 z write ,因此采用默认的方式, cube_1 会把自己的深度值写入到深度缓冲区中。
此时,深度缓冲区 里面 被 cube_1 覆盖的像素 深度值是 c1. 当再绘制 cube_2 时,由于 cube_2 比 cube_1 距离相机远,因此其像素的 深度值 c2 > c1 .
按照默认的 shader ,z test 默认是 EqualLess 的模式,也就是说深度值小(距离相机更近)才绘制,这也是常理上的效果,因此正常情况下 cube_2 被挡住的像素 是不会绘制的。
但是由于 cube_2 的 shader 第2 个 pass 里包含 ZTest Greater ,因此被遮盖的部分只有在 深度值较大时才绘制。
这样就达成了 我们想要的效果。
了解了这个道理,如果希望 cube_2 不穿透某些物体,则使得这些不被穿透的物体晚于 cube_2 绘制,即 queue 的值 大于 cube_2 shader 里面设置的值。这样 cube_2 绘制时深度缓冲区里还没有东西, 只能跟默认的 max 比较,由于由于 cube_2 shader 的 第2个 pass 的 ztest 规则是 Greater ,c2 不大于 max, 这样透视的效果就能失去效果了。
按照这个方法,我测试了 2d,3d 混合测试,带 texture 测试,在设置正确的基础上,都能够达到预期效果。
2d sprite 在设置上有一些需要注意的地方:不光 shader 的 queue 会影响 绘制顺序, sort layer 以及 sort index 也都可能会影响绘制顺序。如果按照相同方法实现时候出了问题,需要留意 这些地方的设置。
记录完毕~