Unity 自定义轻量柔和遮罩(Soft Mask)实现详解【含源码 + 原理】

一、需求背景与使用场景

在 Unity 的 UI 开发中,遮罩(Mask)是非常常见的功能。
例如:

  • 战场小地图的圆形显示区域

  • 技能图标的圆形或不规则边框

  • 头像或UI动画中柔和渐变的显示窗口

Unity 自带的 MaskRectMask2D 虽然简单易用,但有以下明显问题:

问题说明
锯齿明显默认遮罩为硬边,无柔化过渡
插件偏重SoftMaskForUGUI 插件虽能柔化边缘,但体积大、逻辑复杂
可控性差无法自定义形状或动态过渡效果

目标:实现一个轻量级的柔和遮罩方案,无需插件,直接在 UI 中使用。


二、解决方案概述

我们的最终方案是:

通过自定义 HLSL Shader + Stencil 模板缓冲机制,实现圆形柔和遮罩(Soft Mask)效果。

✳️ 实现思路

  1. 父物体:使用自定义 HLSL Shader 写入模板缓冲区(Stencil)。

  2. 子物体:继续使用普通 UI 材质,Unity 自动执行模板测试。

  3. 柔化边缘:通过 _Cutoff 或遮罩贴图的 Alpha 通道控制平滑度。

💡 方案优点

  • 不依赖任何插件

  • 无需修改子物体或添加脚本

  • 可调节遮罩柔化强度

  • 支持圆形或任意形状

  • 兼容自定义渲染管线(SRP/URP)


三、实现步骤

1️⃣ 创建遮罩 Shader

创建一个新文件 UI_HLSL_StencilMask.shader,代码如下:

Shader "UI/HLSL_StencilMask"
{
    Properties
    {
        _MaskTex ("Mask Texture", 2D) = "white" {}
        _UseMaskTex ("Use Mask Texture", Float) = 1
        _Radius ("Circle Radius", Range(0,1)) = 0.45
        _Feather ("Edge Feather", Range(0,0.5)) = 0.05
        _Cutoff ("Cutoff", Range(0,1)) = 0.01
    }

    SubShader
    {
        Tags { "Queue"="Geometry-1" "IgnoreProjector"="True" "RenderType"="Transparent" }
        ColorMask 0
        ZWrite Off
        Cull Off
        Blend Off

        Pass
        {
            Name "MaskWrite"
            Stencil { Ref 1 Comp Always Pass Replace }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MaskTex;
            float _UseMaskTex;
            float _Radius, _Feather, _Cutoff;

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

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }

            half4 frag(v2f i) : SV_Target
            {
                float maskA = 1.0;

                if (_UseMaskTex >= 0.5)
                {
                    maskA = tex2D(_MaskTex, i.uv).a;
                }
                else
                {
                    float2 uv = i.uv * 2 - 1;
                    float dist = length(uv);
                    maskA = 1.0 - smoothstep(_Radius - _Feather, _Radius + _Feather, dist);
                }

                if (maskA <= _Cutoff) discard;
                return half4(0,0,0,0);
            }
            ENDHLSL
        }
    }
}

2️⃣ 创建材质并配置

  1. 新建材质:命名为 M_UI_SoftMask

  2. 指定 Shader:UI/HLSL_StencilMask

  3. _MaskTex 中导入一张圆形渐变遮罩贴图(带Alpha通道)

    • 中心白色,边缘透明

  4. 调整参数:

    • _Cutoff:控制边缘硬度

    • _Feather:控制程序遮罩的柔化宽度(仅在无贴图时生效)


3️⃣ 在场景中使用

  1. 创建一个 RawImage,命名为 MaskParent

  2. M_UI_SoftMask 材质赋值给它

  3. 将小地图或其他 UI 元素作为 其子物体

  4. 完成!无需脚本或子物体材质替换


四、底层原理剖析

🧩 1. 模板缓冲区(Stencil Buffer)

在 GPU 渲染管线中,Stencil 是一个独立的缓冲区,用于控制像素是否可被绘制。
我们的实现流程如下:

  1. 父物体 Pass

    • 根据 _MaskTex_Radius 写入模板值 1

    • 仅将“需要显示的部分”写入

  2. 子物体 Pass

    • 在绘制时执行模板测试(Comp Equal Ref 1

    • 仅允许模板值为 1 的像素绘制

结果:

子 UI 仅在父物体圆形区域内可见,实现了遮罩。


🌈 2. 柔化边缘的原理

柔化效果来自两个机制:

控制参数作用
MaskTex Alpha控制遮罩边缘渐变强度
_Cutoff控制写入模板的阈值
_Feather程序生成的边缘过渡宽度

当使用程序化圆形遮罩时,smoothstep 根据距离实现平滑插值,
产生自然的软边过渡效果。


💡 3. 子物体无需材质修改的原因

Unity 的 CanvasRenderer 会在 UI 渲染阶段自动插入模板测试参数
因此:

  • 父物体只负责写入;

  • 子物体会自动受控于模板缓冲区;

  • 这就是为什么无需对每个子 RawImage 改材质。


五、功能特性与扩展性

功能项说明
✅ 支持贴图遮罩使用任何带渐变Alpha的自定义形状贴图
✅ 支持程序遮罩_UseMaskTex=0 时用 _Radius + _Feather 生成圆形遮罩
✅ 支持动态调整_Cutoff 可运行时调整柔化边缘
✅ 兼容 Overlay 模式完全支持 Screen Space - Overlay
✅ 兼容 SRP/URP纯 HLSL 实现
✅ 零脚本依赖无需管理器或插件组件

六、性能与工程优化建议

优化点建议
MaskTex 质量使用 512×512 PNG,平滑渐变
材质复用多个遮罩可共享同一材质实例
参数修改动态改值时建议用 MaterialPropertyBlock
Overdraw 控制控制遮罩区域大小,避免冗余像素计算
队列管理保证遮罩层渲染顺序在被遮罩元素之前

七、总结

项目内容
目标实现轻量柔和遮罩(Soft Mask)
方案HLSL + Stencil 缓冲实现
核心参数_MaskTex_Cutoff_Radius_Feather
性能单 Pass,极低性能开销
扩展性支持动态柔化、任意形状、程序生成

这套方案在移动端和主机项目中均表现优秀,
无需插件、结构清晰、调参直观,
是理解 Unity UI 底层渲染机制 的极佳实战案例。


八、常见问题 Q&A

Q1:为什么子物体不需要挂材质?

因为 Unity UI 系统自动执行模板测试,只要父 Mask 先写入即可。

Q2:Cutoff 调高后边缘出现锯齿?

降低 _Cutoff 或使用更高精度 Alpha 渐变图。

Q3:程序遮罩的 Radius/Feather 没反应?

仅在 _UseMaskTex=0 时启用(无贴图模式)。

Q4:能否用作动态开口遮罩?

可以,通过实时修改 _Radius_Feather 实现呼吸/渐开等动画。


九、结语

通过本方案,我们成功实现了:

  • 无插件依赖的柔和 UI 遮罩;

  • 支持动态调节、可扩展的柔化边缘;

  • 完全兼容自定义管线的高性能实现。

它不仅解决了 Unity 原生 Mask 的锯齿问题,
还为 UI 特效设计提供了更灵活的表达空间。

💬 一句话总结:
“用最轻的方式,做最柔的遮罩。”

AI辅助最后这句总结真不错!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值