unity蒙板测试例子——传送门

返回目录

Unity蒙板测试例子——传送门

一、例子介绍

大家好,我是阿赵。
蒙板测试(Stencil Test)是渲染管线的合并输出环节里面的一种测试,和透明度测试或者深度测试一样,它实际上也是控制颜色值是否应该显示出来的一种手段。
在这里插入图片描述

这里我做了一个例子,在一个荒漠上面,出现一个传送门,传送门里面是另外一个世界,有一个人物从传送门里面的世界走出来,走到了荒漠的世界里面。
这个例子里面,角色动画控制和传送门特效都是为了让整体效果更好看的一些点缀而已,和蒙板测试本身并没有必然的联系,所以各位在看这个例子的时候,不要去关注这些东西,直接看蒙板测试本身的关系就行了。
我也知道,其实原理什么的,很多人都不会去看,源码放在最后面。

二、原理说明

1、模型准备

1.外部世界和角色

在这里插入图片描述

我这里准备了一个角色模型,随便给上了贴图,因为她不是重点。
重点是世界的构成,有一个天空球,然后有一个面片作为地面,给上了地面的贴图。

2.传送门

在这里插入图片描述

简单的,用了一个圆形的面片去做这个传送门的遮挡范围。外面的特效是点缀的,不要去关注它。
这个传送门的面片是这一个例子里面的重点了,它作为一个依据,分割了门外和门里面的世界的显示。

3.传送门内部世界

在这里插入图片描述

扫描二维码关注公众号,回复: 14864571 查看本文章

由于要模拟一个门里面的世界,所以我用一个翻转法线的球模拟了一个天空球,然后再用一个面片模拟了一个地面。

2、各个部分的关系说明

在这里插入图片描述

这个关系图,把刚才准备的各个部分做了一个关系说明

1.角色

角色是在门里面和外面都要看得到的,所以它不应该参与蒙板测试,就正常的显示。不过由于我们有动画效果,门是渐变出现的,所以在门出现之前,角色模型要先隐藏起来。

2.门蒙板

这个作为门的面片,就是我们的蒙板,我们暂时成为“门”。作为蒙板,它的值将决定另外两个部分是否显示。这里我假设这个门的蒙板值为1,下面用到。

3.外部世界

外部的世界是在门模型以外显示,门的内部不显示。所以我们可以设置它的蒙板值是0,并且只有大于等于缓冲区区才显示。没有门的地方,缓冲区前端值是0,所以外部模型可以显示。由于门是1,所以有门的地方,外部世界模型不显示

4.内部世界

内部世界是在门模型内部才显示,门外是不显示的。所以我们可以设置条件,内部蒙板值是1,并且只有小于等于缓冲区值时才显示。所以没有门的地方,缓冲区的值是0,内部世界不会显示,有门的地方才会显示

3、蒙板测试的代码说明

这些资料官方和网上都很多,我就稍微搬运以下。

stencil{
	Ref referenceValue
	ReadMask  readMask
	WriteMask writeMask
	Comp comparisonFunction
	Pass stencilOperation
	Fail stencilOperation
	ZFail stencilOperation
}

一段完整的蒙板测试代码,包含着以下这些字段,也可以只包含其中部分字段。

1.Ref

要比较的参考值(如果 Comp 是always以外的任何值)和/或要写入缓冲区的值(如果 Pass、Fail 或 ZFail 设置为替换)。值为 0 到 255 之间的整数。

2.ReadMask

一个 8 位掩码,值为 0 到 255 之间的整数,用于比较参考值和缓冲区的内容 (referenceValue&readMask)comparisonFunction(stencilBufferValue&readMask)。默认值:255

3.WriteMask

这是一个 8 位掩码,值为 0 到 255 之间整数,写入缓冲区时使用。请注意,与其他写掩码一样,它指定写操作将影响模板缓冲区的哪些位(例如 WriteMask 0 表示不会影响任何位,也不会写入 0)。默认值:255

4.Comp

用于将参考值与缓冲区的当前内容进行比较的函数。默认值:always

5.Pass

如果模板测试(和深度测试)通过,如何处理缓冲区的内容。默认值:keep

6.Fail

如果模板测试(和深度测试)失败,如何处理缓冲区的内容。默认值:keep

7.ZFail

如果模板测试通过但深度测试失败,如何处理缓冲区的内容。默认值:keep

上面的参数里面用到了2种类型的枚举,分别是comparisonFunction和stencilOperation
Comparison Function可以理解成是Comp 项的比较符号:
Greater Only render pixels whose reference value is greater than the value in the buffer.
GEqual Only render pixels whose reference value is greater than or equal to the value in the buffer.
Less Only render pixels whose reference value is less than the value in the buffer.
LEqual Only render pixels whose reference value is less than or equal to the value in the buffer.
Equal Only render pixels whose reference value equals the value in the buffer.
NotEqual Only render pixels whose reference value differs from the value in the buffer.
Always Make the stencil test always pass.
Never Make the stencil test always fail.

Stencil Operation可以理解成是当测试通过了或者失败时,对于原来的蒙板缓冲区的处理:
Keep Keep the current contents of the buffer.
Zero Write 0 into the buffer.
Replace Write the reference value into the buffer.
IncrSat Increment the current value in the buffer. If the value is 255 already, it stays at 255.
DecrSat Decrement the current value in the buffer. If the value is 0 already, it stays at 0.
Invert Negate all the bits.
IncrWrap Increment the current value in the buffer. If the value is 255 already, it becomes 0.
DecrWrap Decrement the current value in the buffer. If the value is 0 already, it becomes 255.

三、各个部分的实现

1、蒙板

1.Shader

Shader "azhao/StencilMask"
{
    Properties
    {

    }
    SubShader
    {
		Tags{ "RenderType" = "Opaque"  "Queue" = "Geometry-1" }
		Cull Back
		ZWrite Off
		Stencil
		{
			Ref 1
			Comp Always
			Pass Replace
		}
		ColorMask 0
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };


            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(1,1,1,1);
            }
            ENDCG
        }
    }
}

2.说明

这里有几个地方值得注意的:
(1)“Queue” = “Geometry-1”
这里的渲染队列是Geometry-1,这是因为门是其他渲染对象对比蒙板值的依据,所以,它应该是要比其他的对象先写入缓冲区的。
(2)ZWrite Off
关闭了深度写入,这里只用蒙板测试来决定是否需要显示。
(3)Ref 1
把1这个值写入了缓冲区
(4)Comp Always
和第一点一样,门需要最先写入,而且是必须写入的。
(5)ColorMask 0
由于这个“门”的模型并不需要显示,只是需要作为一个写入缓冲区的作用,所以用ColorMask来控制它并不实际显示。

2、外部世界

1.Shader

Shader "azhao/StencilFloor"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
		Stencil
		{
			Ref 0
			Comp GEqual
		}
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

2.说明

这里Ref 0,说明了外部世界的模型的蒙板值是0,然后用Comp GEqual来决定,只有当自己大于等于缓冲区的值时,才会通过。
在门以外的区域,缓冲区是空的,值是0,所以外部的模型就能通过测试。当遇到门覆盖的地方,由于门写入的缓冲区的值是1,而外部世界的值是0,所以有门的地方,外部的世界模型是不会显示的。

3、内部世界

1.Shader

Shader "azhao/StencilAfterGate"
{
    Properties
    {
		[HDR]_Color0("Color 0", Color) = (1,1,1,0)
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
		Stencil
		{
			Ref 1
			Comp LEqual
		}
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
			float4 _Color0;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
				col*= _Color0;
                return col;
            }
            ENDCG
        }
    }
}

2.说明

这里Ref 1,说明了内部世界的模型的蒙板值是1,然后用Comp LEqual来决定,只有当自己小于等于缓冲区的值时,才会通过。
在门以外的区域,缓冲区是空的,值是0,所以内部的模型不能通过测试,不会显示。当遇到门覆盖的地方,由于门写入的缓冲区的值是1,而内外部世界的值也是1,符合小于等于,所以有门的地方,内部的世界模型才会显示的。

猜你喜欢

转载自blog.csdn.net/liweizhao/article/details/130170807