Unity Shader之Mask与Rect Mask 2D

引言

  Unity中遮罩是非常常用的组件。而其子对象也常常需要自定义shader来显示不同的效果,比如:圆角矩形、涂色等等。这种情况下,需要在子对象的shader中额外添加几行代码来保证遮罩生效。因为默认Shader(UI/Default)实现了遮罩功能,而自定义的Shader往往只实现了效果部分,忽略了遮罩部分。

内容

  本文会分别讲述使MaskRect Mask 2D生效的Shader写法,由于它们的Shader代码写法直接和底层原理相关,所以会大致讲述它们的底层原理,更详细的原理可以看下面两篇文章。
  Unity3D Shader系列之模板测试
  [UGUI源码分析] Unity遮罩之RectMask2D详细解读

Mask遮罩

  Mask遮罩是基于模板测试的原理——挂载Mask组件的UI对象对应的模板缓存区中的模板值会被设置成1(1是默认的,也可以自己设置),模板缓存区其他区域被设置成0。给Mask的子对象设置一个模板参考值(即Mask区域模板缓存区中的模板值),Mask子对象与模板参考值进行比较,如果相等保留像素,则可以只保留Mask子对象在Mask区域的像素。 Shader代码如下

Stencil
{
    Ref 1        // 模板参考值设置为1
    Comp Equal   // 模板缓冲区值等于参考值的像素通过,即只通过Mask区域的像素
    Pass Keep    // 不更新模板
}

  也可以将参考值、测试条件和测试通过操作参数化,Shader代码如下。需要注意:运行时Shader的参数值并不一定等于代码里面设定的参数值,需要在Inspector里面修改Shader参数值。

Properties
{
    _StencilComp("Stencil Comparison", Float) = 3
    _Stencil("Stencil ID", Float) = 1
    _StencilOp("Stencil Operation", Float) = 0
}
Stencil
{
     Ref [_Stencil]
     Comp [_StencilComp]
     Pass [_StencilOp] 
}

Rect Mask 2D遮罩

  Rect Mask 2D遮罩是基于UI裁剪的方式实现的,计算父物体中所有RectMask2D覆盖区域的交集——得到的交集是矩形裁剪区域。将矩形裁剪区域传给UI子对象的shader(UI系统自动传给Shader中的_ClipRect),Shader中的片元着色器会丢弃不在裁剪区域的片元。Shader代码如下

Shader "Unlit/OriginalFill"
{
    Properties
    {
        _MainTex ("Mask", 2D) = "white" {}
    }
    SubShader
    {
        Tags 
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
        }

        LOD 100
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            
            #include "UnityCG.cginc"
            #include "UnityUI.cginc"                          //使用UnityGet2DClipping()需要加上的头文件

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 worldPosition : TEXCOORD1;             // 世界坐标(用于裁剪计算)
            };

            sampler2D _MainTex;
            float4 _ClipRect;                                 // 裁剪区域(自动由 UI 系统填充)

            v2f vert (appdata v)
            {
                v2f o;
                o.worldPosition = v.vertex;                   // 记录世界坐标
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 1. 裁剪测试:判断像素是否在 Mask 定义的裁剪区域内
                float clip = UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
                if (clip <= 0)
                {
                    discard;
                }
                return tex2D(_MainTex, i.uv);
            }
            ENDCG
        }
    }
}

分析区别

  (1)Mask和Rect Mask 2D遮罩的底层原理完全不同;
  (2)Rect Mask 2D遮罩只能用于矩形遮罩,但性能开销更低。建议优先使用Rect Mask 2D遮罩来实现功能,如Rect Mask 2D遮罩无法满足需求,再考虑使用Mask遮罩;

小结

  想要编写使遮罩生效的Shader代码,需要清楚Mask和Rect Mask 2D遮罩的底层原理。此外,Mask和Rect Mask 2D是两种截然不同的遮罩方式,无论是它们的实现原理还是它们适用的场合。

posted @ 2025-11-13 11:21  游侠某  阅读(16)  评论(0)    收藏  举报