0
点赞
收藏
分享

微信扫一扫

UGUI源码解析——Mask


一:前言

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

二:模板缓存原理

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

模板缓存步骤:如下两张图片,父对象是绿色图片,子对象是红色图片

如果没有添加Mask组件则先将绿色图片每个像素颜色绘制在屏幕上,再将红色每个像素颜色绘制在屏幕上,重叠区域内红色完全覆盖了绿色

如果添加了Mask组件,在渲染的第一帧先将绿色图片每个像素颜色绘制在屏幕上同时将每个像素的stencil buffer值设置为1,接下来绘制红色图片,绘制之前先将绘制区域的stencil buffer值取出来,如果是1则继续绘制如果是0则不进行绘制,从而实现了遮罩效果

UGUI源码解析——Mask_unity_03


UGUI源码解析——Mask_游戏引擎_04


UGUI源码解析——Mask_像素点_05

​​三:源码解析

——OnEnable

UGUI源码解析——Mask_缓存_06


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

UGUI源码解析——Mask_ide_07


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

UGUI源码解析——Mask_游戏引擎_08


继承自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_缓存_09

四:挖孔遮罩

了解了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;
}
}

举报

相关推荐

0 条评论