Unity shader的StencilTest、ZTest与半透明渲染

着色器完成顶点编程和片元编程操作之后得到的颜色值,最后像素能不能够渲染出来还要经过最后一步逐片元操作,其中包括模板测试,深度测试等,只有通过了测试的像素点颜色才有资格进入颜色缓冲进行覆盖写入或者与已有颜色进行混合,从而被GPU渲染出来进入我们的视野。

在这里插入图片描述

其实Stencil Buffer和Z Buffer就是一类东西,其目的都是为这个像素点的颜色缓冲区值服务,不过是其记录的数值作用目的不同。
模板缓冲Stencil Buffer和深度缓冲Z Buffer在数据中是个什么样的存在呢?
在渲染管线中,屏幕上每一个像素点各自都有自己的一份Stencil Buffer和Z Buffer的值在把关,每个屏幕像素点中又有n(n>=0)个片元重叠在一起竞争试图进行渲染该像素点,这些开启了Stencil Test和Z Test的片元都有它们各自独立的Ref值(用于与Stencil Buffer比较)和深度值(用于与Z Buffer比较)记录着该片元的状态,而这些状态值决定着该片元是否能够通过这2关测试被渲染。

而我们主要操作的就是测试的规则,这也就是我们要学习的目标。

Stencil Test 流程如下思维导图:

在这里插入图片描述
通常也搭配ColorMask 来使用,ColorMask 用来指定输出颜色的通道,值为RGBA或者为0不输出任何颜色。

在模板测试中,我们操作的主要是第2和第4步。
模板测试的语法:

stencil{
	Ref             //整型,用于该片元与Stencil Buffer进行比较,此值手动设定值,范围0-255。
	ReadMask  //整型,8位掩码,用于读取Ref与Stencil Buffer(分别进行&操作),默认值为二进制11111111。
	WriteMask  //整型,8位掩码,用于写入Stencil Buffer(进行&操作),默认值为二进制11111111。
	Comp         //配置,比较Ref与Stencil Buffer的操作符号,默认always 。
	Pass 		  //配置,更新StencilBuffer方式,通过模板测试和深度测试时,走该配置,默认值keep。
	Fail    		  //配置,更新StencilBuffer方式,不通过模板测试和深度测试时,走该配置,默认值keep。
	ZFail 		  //配置,更新StencilBuffer方式,ZTest不通过写入配置时,走该配置,默认值keep。
}

Comp和Pass、Fail、ZFail可选配置值:
Comp :Greate、GEqual、 Less、 LEqual、 Equal、 NotEqual、 Always、 Never。
Pass/Fail/ZFail :Keep、 Zero、 Replace、 IncrSat、 DecrSat、 Invert、 IncrWrap、 DecrWrap。

其比较公式为

if( (Ref & ReadMask) Comp (StencilBuffer & ReadMask))
		测试通过,使用片元;
else
		测试未通过,丢弃片元;

得到最简单的模板测试效果:
StencilMask.Shader

Shader "Custom/StencilMask"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue" = "Geometry-1"}//此Shader为Mask,所以对于他要mask的模型,应该优先渲染填充Stencil Buffer数据。
        LOD 100
		ColorMask 0 //不输出任何颜色。
		ZWrite Off  //关闭深度写入,原因后面讲到。
		
		Stencil
		{
			Ref 1		//Ref与Stencil Buffer的值进行比较。
			Comp Always  //Always总是能通过模板测试。
			Pass Replace //通过模板测试时,更新像素点Stencil Buffer的值为Ref,那么下个片元测试时读取到Stencil Buffer==1。
		}
        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
        }
    }
}

StencilModel.shader

Shader "Custom/StencilModel"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags {"Queue" = "Geometry" "RenderType"="Opaque" }
        LOD 100

		Stencil {
			Ref 1         //Ref与StencilBuffer比较。(根据Queue渲染队列设置,之前先渲染了StencilMask,其将对应像素点StencilBuffer更新为了1.)
			Comp Equal    //Equal : Ref == StencilBuffer == 1,通过,其它没被StencilMask更新的像素点StencilBuffer != 1, 未通过。
		}
		//以下Pass以最简单的方式渲染一个带纹理的模型。
        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
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

得到效果:
在这里插入图片描述
这只是利用模板测试得到一个简单的效果,模板测试还可以做到其它更6b的效果,如模型轮廓描边、模型阴影等。

接下来的是深度测试(ZTest)
Z Test 流程如下思维导图:
纠正下,下图第四步括号内应该为(半透明)非(透明)
在这里插入图片描述
GPU渲染多个物体时,顺序是怎么样的呢?以离相机近为前,物体从前到后渲染好像是合理的,但实际上有时候一些模型大部分重叠在一起,或如下图,这样分前后似乎还是不能保证越靠前像素就越先渲染,所以分前后似乎也不绝对。
哪个在前哪个在后?
渲染顺序实际上还是由Render Queue来控制的。
当然,在渲染不透明物体和半透明物体时关于渲染顺序时有着本质的区别:
渲染不透明物体时,通常从前往后,由于有深度测试的存在,ZTest霸道的依赖ZBuffer的值进行把关测试将未通过的片元完全丢弃,将通过测试的片元以覆盖形式写入颜色缓冲,并不管你渲染的前后顺序,所以渲染不透明物体对RenderQueue不敏感。
渲染半透明物体时,通常从后往前,RenderQueue很重要,形成半透明条件要和已经存在的颜色缓冲混合后再写入颜色缓冲,并且要求越近相机的半透明物体越先渲染,避免混合颜色后分不清哪个物体在前哪个物体在后。
因此在根据以上深度测试思维导图也得出渲染不透明物体的要求:
1,不透明物体,开启ZWrite,设置RenderQueue比半透明物体先渲染;
2,半透明物体,开启Blend,关闭ZWrite;
3,半透明物体排序,按离相机的远近顺序排序,设置RenderQueue从后往前渲染。

Unity的5个渲染队列,越小越先被渲染:
Background = 1000	     //最先被渲染,冲用在绘制背景。
Geometry = 2000         //默认队列,多数不透明物体使用这个。
AlphaTest = 2450         //透明度测试。
Transparent = 3000     //半透明物体。
Overlay = 4000           //最后的效果处理。

所以有最简单的透明shader
Transparent.shader

Shader "Custom/Transparent"
{
    Properties
    {
		_Color("Color Tint", Color) = (1, 1, 1, 0.5)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Transparent"}//将渲染队列放在Transparent。
        LOD 100
		ZWrite Off						//关闭ZWrite。
		Blend SrcAlpha OneMinusSrcAlpha //开启Blend。
        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
        }
    }
}

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值