使用 unity理解渲染时的 深度缓冲机制

在其他游戏里见到过这样的效果:

人物走到建筑后面,被建筑挡住的部分做特殊显示、没有被建筑挡住的部分正常渲染。

想要实现这种效果,关键的要点是 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 也都可能会影响绘制顺序。如果按照相同方法实现时候出了问题,需要留意 这些地方的设置。

记录完毕~

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值