您的位置:首页 > 产品设计 > UI/UE

UGUI源码分析:MaskableGraphic,RectMask2D与Mask的原理

2020-06-29 04:45 549 查看

系列

UGUI源码分析系列总览

文章目录

MaskableGraphic

BaseClass: Graphic

Interface: IClippable、IMaskable、IMaterialModifier

简介

前置:Graphic源码剖析

MaskableGraphicGraphic的基础上实现了裁剪与遮罩功能

这主要是由 IClippableIMaskable 两个接口来实现的。

​ 在Graphic更新材质的流程中有提及Mask。Graphic 可以理解成由骨头和皮肤所组成,骨头即顶点信息所构建的网格(Mesh),皮肤则是依附于Mesh的材质和纹理。实际上Mesh是不可见的,对于可见物的处理(例如Mask遮罩剔除)都是针对于Material。

理解清楚IClippable与IMaskable相关的组件原理便是理解MaskableGraphic的关键

RectMask2D 矩形裁剪

BaseClass: UIBehaviour

Interface: IClipper、ICanvasRaycastFilter

Intro: 这是UGUI提供的不依赖于Graphic的裁剪组件,它的原理在于设置IClippable组件中canvasRenderer.EnableRectClipping 来实现矩形裁剪效果

说到 IClipper(裁剪者)与 IClippable(可裁剪对象),那必须以RectMask2D组件为例来讲解

RectMask2D的工作原理

  • RectMask2DIClipper,当启动时(Enable)先向ClipperRegistry中注册自己,然后会调用其所有子节点下IClippable 组件的RecalculateClipping方法,将其添加进最近父节点中的RectMask2D中(这是为了避免各种嵌套带来的浪费)
// MaskableGraphic 中更新裁剪者的方法
private void UpdateClipParent()
{
var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;

// if the new parent is different OR is now inactive
if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive()))
{
m_ParentMask.RemoveClippable(this);
UpdateCull(false);
}

// don't re-add it if the newparent is inactive
if (newParent != null && newParent.IsActive())
newParent.AddClippable(this);

m_ParentMask = newParent;
}
  • Canvas进行刷新的时候(CanvasUpdateSystem),会调用所有启用中的IClipper,执行Cull 操作,遍历执行 IClipper.PerformClipping

    ClipperRegistry.instance.Cull();

    m_Clippers[i].PerformClipping();

  • PerformClipping : 目的在于更新IClippable中用于裁剪的Rect

    首先会借助MaskUtilities、Clipping 寻找的最小的裁剪框clipRect

    接着会遍历自身下所有的IClippable组件(由IClippable.RecalculateClipping 添加)设置clipRect

    clipTarget.SetClipRect(clipRect, validRect) validRect:用于判断裁剪框是否可用(长宽>0)

    canvasRenderer.EnableRectClipping(clipRect) MaskableGraphic 中设置裁剪框

    最后会判断是否改变IClippable中cull的状态

    canvasRenderer.cull = cull;

    在此流程期间RectMask2D会优化处理过程:

    ​ 1.记录上次的clipRect来判断裁剪矩形是否发生变化,从而省略没必要的重新裁剪。

    m_LastClipRectCanvasSpace = clipRect;

    ​ 2.裁剪层的子集合会因为父级的裁剪而被裁剪,因此可以传递无效的rect来避免重复的处理。

    clipTarget.Cull(maskIsCulled ? Rect.zero : clipRect,maskIsCulled ? false : validRect)

IMaskable基于Material的遮罩

MaskableGraphic 中IMaskable实现:

  • Enable时,若该物体自身含有Mask组件则会调用其子节点路径下所有IMaskable组件方法
protected override void OnEnable()
{
base.OnEnable();
m_ShouldRecalculateStencil = true; //控制是否从新计算遮罩深度->改变遮罩材质
UpdateClipParent();
SetMaterialDirty();

if (GetComponent<Mask>() != null)
{
// 设置Mask遮罩状态
MaskUtilities.NotifyStencilStateChanged(this);
}
}

//IMaskable 接口方法
public virtual void RecalculateMasking()
{
m_ShouldRecalculateStencil = true;
SetMaterialDirty();
}
  • Grahpic材质重建的过程中会调用其上所有IMaterialModifier组件方法来处理最终的渲染材质materialForRendering
//MaskableGraphic 中IMaterialModifier 组件方法
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
var toUse = baseMaterial;//来自Graphic的基础材质
if (m_ShouldRecalculateStencil)
{
var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
m_ShouldRecalculateStencil = false;
}
// 优化了遮罩处理,如果已经启用了Mask组件,则不必再次做重复的事情
Mask maskComponent = GetComponent<Mask>();
if (m_StencilValue > 0 && (maskComponent == null || !maskComponent.IsActive()))
{
//借助StencilMaterial生产一个新的遮罩材质,这里是使用list存储避免重复生成一样的材质
var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMat;
toUse = m_MaskMaterial;
}
return toUse;//返回新生成的遮罩材质
}

.
.
.
.
.

嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: