区别
具体用法以及内容看:Unity 3D - Mask和RectMask2D区别_mask和mask2d-CSDN博客
相关概念
Drawcall: CPU提交数据给GPU,然后向GPU下渲染命令这个过程我们通常叫做DrawCall, 又叫同一个批次渲染。
Drawcall数目(批次数): 游戏引擎绘制完一个游戏场景中的所有物体需要向GPU提交几次渲染命令(Drawcall)。100个物体,一个个提交给GPU一个一个绘制,那么就要向GPU提交100次渲染命令(Drawcall),所以Drawcall数目是100。还有一种说法就是绘制一个游戏场景的物体分成了多少个批次来进行渲染的。
合批(降低Drawcall数目): 把几个物体合在一起提交给GPU渲染绘制叫做合批。通过合批,可以降低绘制游戏场景的Drawcall数目。比如100个物体,我把前50个物体合在一起提交给GPU渲染,后50个物体合一起提交给GPU渲染,那么渲染100个物体用通过2批渲染绘制完成,也就是2次Drawcall。合批其实就降低了Drawcall的数目,我们通常说的降低Drawcall指的就是合批。
Mask 和 RectMask2D
遮挡组件,可以将其子节点下矩形区域外的内容剔除,是滚动窗口中最常用的组件。
这两种方式主要是在剔除的方法上有所区别,在实现效果上都是一样的,其中Mask使用是GPU模板剔除实现;RectMask2D则采用CPU顶点剔除实现。
Mask遮罩核心部分
Mask 组件调用了模板材质球构建了一个自己的材质球,因此它使用了实时渲染中的模板方法来裁切不需要显示的部分,所有在 Mask 组件后面的物体都会进行裁切。我们可以说 Mask 是在 GPU 中做的裁切,使用的方法是着色器中的模板方法。
var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMaterial;
var unmaskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Zero, CompareFunction.Always, 0);
StencilMaterial.Remove(m_UnmaskMaterial);
m_UnmaskMaterial = unmaskMaterial;
graphic.canvasRenderer.popMaterialCount = 1;
graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
return m_MaskMaterial;
Mask实现原理:
1. Mask会赋予Image一个特殊的材质,这个材质会给Image的每个像素点进行标记,将标记结果存放在一个缓存内(这个缓存叫做 Stencil Buffer)
2. 当子级UI进行渲染的时候会去检查这个 Stencil Buffer内的标记,如果当前覆盖的区域存在标记(即该区域在Image的覆盖范围内),进行渲染,否则不渲染Stencil Buffer:GPU为每个像素点分配一个称之为StencilBuffer的1字节大小的内存区域,这个区域可以用于保存或丢弃像素的目的。stencil buffer是为了实现多个“绘画者”之间互相通信而存在的。由于gpu是流水线作业,它们之间无法直接通信,所以通过这种共享数据区的方式来传递消息,从而达到一些“不可告人”的目的。
RectMask2D核心部分
public virtual void PerformClipping()
{
// if the parents are changed
// or something similar we
// do a recalculate here
if (m_ShouldRecalculateClipRects)
{
MaskUtilities.GetRectMasksForClip(this, m_Clippers);
m_ShouldRecalculateClipRects = false;
}
// get the compound rects from
// the clippers that are valid
bool validRect = true;
Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
if (clipRect != m_LastClipRectCanvasSpace)
{
for (int i = 0; i < m_ClipTargets.Count; ++i)
m_ClipTargets[i].SetClipRect(clipRect, validRect);
m_LastClipRectCanvasSpace = clipRect;
m_LastClipRectValid = validRect;
}
for (int i = 0; i < m_ClipTargets.Count; ++i)
m_ClipTargets[i].Cull(m_LastClipRectCanvasSpace, m_LastClipRectValid);
}
我们可以看到,RectMask2D会先计算并设置裁切的范围,再对所有子节点调用裁切操作。其中:
MaskUtilities.GetRectMasksForClip(this, m_Clippers);
获取了所有有关联的 RectMask2D 遮罩范围,然后
Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
计算了需要裁切的部分,实际上是计算了不需要裁切的部分,其他部分都进行裁切。最后
for (int i = 0; i < m_ClipTargets.Count; ++i)
m_ClipTargets[i].SetClipRect(clipRect, validRect);
对所有需要裁切的UI元素,进行裁切操作。其中 SetClipRect 裁切操作的源码,如下:
public virtual void SetClipRect(Rect clipRect, bool validRect)
{
if (validRect)
canvasRenderer.EnableRectClipping(clipRect);
else
canvasRenderer.DisableRectClipping();
}
最后操作是在 CanvasRenderer 中进行的。
RectMask2D大致工作流程:
1.C#层:找出父物体中所有RectMask2D覆盖区域的交集(FindCullAndClipWorldRect)
2.C#层:所有继承MaskGraphic的子物体组件调用方法设置剪裁区域(SetClipRect)传递给Shader
3.Shader层:接收到矩形区域_ClipRect,片元着色器中判断像素是否在矩形区域内,不在则透明度设置为0(UnityGet2DClipping )
4.Shader层:丢弃掉alpha小于0.001的元素(clip (color.a - 0.001))
RectMask2D 代替 Mask
Mask合批规则
Mask内的元素可以跟其他Mask内的元素进行合批,需要同渲染层级(depth), 同材质, 同图集
Mask内的元素与非Mask的元素无法进行合批
为什么Mask内外不能合批:
不同材质不能合批。Mask用的是Stencil模板材质,而非Mask用的default UI mat材质。
RectMask2D合批规则
RectMask2D 只能合批自己遮置范围内的UI对象(RectMask2D之间不能进行合批)
RectMask2D不会像Mask一样,额外生成2个DrawCall(本身不产生drawcall)