老规矩,先上一张效果图:
这个功能效果的核心点是模板缓存Stencil buffer,和深度缓存类似,模板缓存可以为屏幕上的每个像素点保存一个无符号整数值(通常的话是个8位整数,范围0-255),通过比较这个数值可以控制是否需要更新当前像素的颜色缓存,这个过程叫做模板测试,默认情况下,总是测试通过的,可以通过以下参数来控制这个过程:
- Ref 自定义数值,用来和模板缓冲中的值进行比较,范围0-255,默认值是0
- ReadMask 对当前参考值和已有值进行mask操作,默认值255;
- WriteMask 写入Mask操作,默认值255;
- Comp 比较方法,Ref定义的值和当前像素缓存上的值进行比较,有以下参数,默认值always:
Greater - 大于
GEqual - 大于等于
Less - 小于
LEqual - 小于等于
Equal - 等于
NotEqual - 不等于
Always - 永远通过
Never - 永远通不过 - Pass 模版测试和深度测试都通过时,进行的操作
- Fail 模版测试和深度测试都失败时,进行的操作
- ZFail 模版测试通过而深度测试失败时,进行的操作
Pass,Fail,ZFail 默认值都是Keep,可使用的参数如下:
Keep 保持(即不做处理)
Zero 归零
Replace 替换(参考值替换原有值)
IncrSat 增加1,最大到255
DecrSat 减少1,最小到0
Invert 反转所有位
IncrWrap 值增加1,大于255时,变成0.
DecrWrap 值减少1,小于0时,变成255
好了,有了上述的介绍,再回到场景中,场景分为3个部分,蓝色的区域,黑色的区域和连接两个区域的门,
首先是门,负责显示玩家可以进入的场景,所以门主要的功能就是:把屏幕上对应位置的Ref值刷新为进入的场景的Ref值,本身不需要显示任何颜色;
然后是两个场景,分别有不同的Ref值(黑色场景的Ref为1,蓝色场景的Ref为2)。
下面开始具体的实现,以玩家处于蓝色场景,将要进入黑色场景为例,首先新建3个Surface Shader分别对应蓝色场景,黑色场景,门:
- 蓝色场景是需要显示出来的,所以场景中的模板测试是需要通过的,即Comp Alawys,这样场景就可以显示出来;
//在SubShader中添加
stencil
{
ref 2
comp Always
}
·
- 黑色场景只需要在门中看到,其他的地方是不需要显示的,它的比较方式就是 Comp Equal.
//在SubShader中添加
stencil
{
ref 1
comp Equal
}
·
- 门需要刷新的Ref值就是黑色场景的Ref值.
//在SubShader中添加
Zwrite off //关闭写入深度,防止在深度测试中将门后的场景覆盖
Cull off //开启双面显示
Colormask 0 //屏蔽颜色的输出
stencil
{
Ref 1
Comp Alawys
Pass replace //测试通过则将stencilBufferValue刷新为1
}
到这一步,就实现了一个静态的效果:从蓝色场景看到门中的黑色场景。
接下来是动态的实现,穿越了门后,黑色场景全部显示,而蓝色场景则可以从门中看到:
- 门shader的属性Properties中新增2个属性,方便在c#脚本中动态的赋值:
//新增属性
_RefValue("Ref",range(0,255)) = 0
[Enum(UnityEngine.Rendering.CompareFunction)]_StencilComp("Stencil Comp",float) = 3
//修改stencil
stencil
{
ref [_RefValue] //替换成修改后的属性
comp [_StencilComp] //替换成修改后的属性
pass replace
}
- 场景shader的属性Properties新增一个属性
//新增属性
[Enum(UnityEngine.Rendering.CompareFunction)]_RefValue("Ref Valus",int) = 3
stencil
{
ref 2
comp [_RefValue] //替换成新增属性
}
- 在c#脚本中动态的控制这些属性,关键代码如下:
if(cameraPostionInPortalSpace.z < -0.3) //表示在scene2这边
{
scene2Mat.SetInt("_RefValue", (int)CompareFunction.Always);
scene1Mat.SetInt("_RefValue", (int)CompareFunction.Equal);
portalMat.SetInt("_RefValue", 1);
}
else if (cameraPostionInPortalSpace.z >0.3) //表示在scene1这边
{
scene2Mat.SetInt("_RefValue", (int)CompareFunction.Equal);
scene1Mat.SetInt("_RefValue", (int)CompareFunction.Always);
portalMat.SetInt("_RefValue", 2);
}
else //这里表示在门口附近,两边场景都需要显示
{
scene2Mat.SetInt("_RefValue", (int)CompareFunction.Always);
scene1Mat.SetInt("_RefValue", (int)CompareFunction.Always);
}
到这里就差不多啦~~~
git仓库:https://github.com/Looooooong/ShadersRoom
最后附上完整的shader:
·
Shader "ShadersRoom/StencilPortal" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_RefValue("Ref",range(0,255)) = 0
[Enum(UnityEngine.Rendering.CompareFunction)]_StencilComp("Stencil Comp",float) = 3
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
zwrite off
cull off
colormask 0
stencil
{
ref [_RefValue]
comp [_StencilComp]
pass replace
}
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Shader "ShadersRoom/Scene1" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
[Enum(UnityEngine.Rendering.CompareFunction)]_RefValue("Ref Valus",int) = 1
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
stencil
{
ref 1
comp [_RefValue]
}
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Shader "ShadersRoom/Scene2" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
[Enum(UnityEngine.Rendering.CompareFunction)]_RefValue("Ref Valus",int) = 3
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
stencil
{
ref 2
comp [_RefValue]
}
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}