UGUI源码解析——Mask

本文深入探讨Unity UGUI的Mask组件,解释其工作原理,特别是模板缓存的作用。通过分析源码,展示了OnEnable和OnDisable如何影响遮罩材质,并详细阐述了GetModifiedMaterial方法在处理单一及嵌套遮罩情况下的不同策略。此外,还介绍了如何创建反向遮罩,提供了一个自定义的HoleImage类示例。

一:前言

Mask是遮罩组件,继承自UIBehaviour、ICanvasRaycastFilter、IMaterialModifier
它遮罩的形状由Graphic决定,所以可以利用不同的Graphic实现不同形状的遮罩
它的实现原理是利用了StencilBuffer(模板缓存)

在Mask的GetModifiedMaterial方法中生成一个材质将StencilBuffer值设置为特定值
在MaskableGraphic的GetModifiedMaterial方法中生成一个材质,这个材质在渲染时会取出StencilBuffer的值,判断是否与特定值相等,如果相等才进行渲染


二:模板缓存原理

Mask组件会赋给当前Graphic一个特殊材质,这个材质会给Graphic的每个像素点进行标记并放在一个称为Stencil Buffer的缓存内,首先将父级每个像素点的标记设置为一个特定值,子级UI进行渲染的时候会去检查重合区域内的特定值是否与这个特定值相等,如果相等则进行渲染,否则不渲染

模板缓存步骤:如下两张图片,父对象是绿色图片,子对象是红色图片
如果没有添加Mask组件则先将绿色图片每个像素颜色绘制在屏幕上,再将红色每个像素颜色绘制在屏幕上,重叠区域内红色完全覆盖了绿色
如果添加了Mask组件,在渲染的第一帧先将绿色图片每个像素颜色绘制在屏幕上同时将每个像素的stencil buffer值设置为1,接下来绘制红色图片,绘制之前先将绘制区域的stencil buffer值取出来,如果是1则继续绘制如果是0则不进行绘制,从而实现了遮罩效果



​​三:源码解析

——OnEnable

调用NotifyStencilStateChanged方法重新设置所有遮罩材质,最终调用了Graphic类中的SetMaterialDirty方法去更新材质


——OnDisable

从StencilMaterial中移除m_MaskMaterial和m_UnmaskMaterial并置为空,调用NotifyStencilStateChanged方法重新设置所有遮罩材质,最终调用了Graphic类中的SetMaterialDirty方法去更新材质


——GetModifiedMaterial

继承自IMaterialModifier接口,MaskableGraphic也继承了这个接口,通过这个新材质设置修改模板缓冲值
分为两个部分来看
第一部分:处理只有一个Mask的情况
首先查找对象的根画布,接着计算自身到根画布之间Mask的个数,如果只有自身一个Mask则stencilDepth为0,desiredStencilBit则为1,desiredStencilBit表示实际要写入模板缓冲的参考值,
此时通过StencilMaterial.Add获得一个新材质(StencilOp.Replace-2,CompareFunction.Always-8)并将这个材质返回,此时父对象模板缓冲区的参考值为1
接着再获取一个新材质(StencilOp.Zero-1,CompareFunction.Always-8),这个材质实际是用来清除模板缓冲区的,以避免不要影响后续的渲染
第二部分:处理嵌套Mask的情况
与第一部分类似,只不过最后会传入两个参数:readMask(读取掩码)和writeMask(写入掩码)

设置材质时会开启UNITY_UI_ALPHACLIP,这也解释了为什么将Mask对象上Graphic对应的Alpha值设置为0,所有子对象都显示不出来了,在UI-Default.shader中有以下操作:透明度过小会被裁剪


四:挖孔遮罩

了解了UGUI的Mask实现原理后,我们可以通过设置CompareFunction参数实现反向遮罩,UGUI的Image是指定区域显示,HoleImage是指定区域不显示

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

/// <summary>
/// 挖孔Image
/// </summary>
public class HoleImage : Image
{
    public override Material GetModifiedMaterial(Material baseMaterial)
    {
        var toUse = baseMaterial;

        if (m_ShouldRecalculateStencil)
        {
            var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
            m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
            m_ShouldRecalculateStencil = false;
        }

        if (m_StencilValue > 0 && !isMaskingGraphic)
        {
            var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.NotEqual, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
            StencilMaterial.Remove(m_MaskMaterial);
            m_MaskMaterial = maskMat;
            toUse = m_MaskMaterial;
        }
        return toUse;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello Bug.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值