UGUI合批
- UGUI合批规则概述
- UGUI性能查看工具
- 合批部分的特殊例子
- 一个白色image、蓝色image覆盖了Text,白色image和Text哪个先渲染
- Mask合批
- Mask为什么会产生两个drawcall
- Mask为什么不能合批
- Mask注意要点
- RectMask2D
- 为什么RecMask2D比Mask性能更好
- 主要代码
- RectMask2D注意要点
- 根据应用场景选择使用哪个(Mask、RectMask2D)
- 只需要一个遮罩
- 需要多个遮罩,遮罩下有多个子物体
UGUI合批规则概述
-
遍历所有UI元素
-
根据深度Depth(优先级最高,-1表示不用渲染)、材质ID、图片ID、渲染顺序对所有UI进行排序
-
进行合批处理,比如UI1和UI2(两者必须是紧挨着的)是相同材质、相同图片就可以进行合批,如果UI1和UI2之间有一个中间层UI3(根UI1和UI2材质不同),就会打断合批
- Text必须文字的网格覆盖到image上面才算相交,Rect Transform框框相交不算
- 白色image的深度为0,最先渲染,然后判断Text是否相交,再判断材质ID和图片ID是否相同,如果相同则可以合批,因为不相同,所以Text深度为白色image+1为1(如果Text底下有很多image,则取最大的深度+1)
- 红色image因为底下有Text和白色image,所以计算出两个深度值分别1和0,取最大值加1,就是2
- 黄色和蓝色image会进行合批测试操作,深度值都是2(这里只是测试是否能合批,并还没有真正合批)
- 最后会得到一个排序数组list并把所有深度为-1的值剔除掉,0、1、2、2、2,传给合批部分的程序进行合批操作,判断相邻的元素是否能进行合批,通过判断数组的值是否相等
UGUI性能查看工具
合批部分的特殊例子
一个白色image、蓝色image覆盖了Text,白色image和Text哪个先渲染
- 根据分析工具可以看到白色image先渲染,文本后渲染(白色图片ID小一点)
- 这样得到的数组list,会先是白色image、Text、蓝色image,导致白色和蓝色无法合批
- 解决办法是把白色image和蓝色image赋值同一个texture
- 只要是满足合批条件,合批数组紧挨着,虽然没有覆盖,也可以合批
- 能不能合批,在最后合批数组上才能决定
Mask合批
-
mask无法跟mask外的物体进行合批,但是mask之间可以合批
-
mask本身会产生两个drawcall,性能损耗
Mask为什么会产生两个drawcall
- 第一个drawcall是mask在设置模板缓存产生的
- 第二个drawcall是mask在还原模板产生的
- mask之间可以进行合批,设置模板缓存和还原模板时候,因为图片ID和材质是一样的,所以可以合批
- 设置模板缓存时候,会把需要显示部分的缓存值设置为1,遮罩的部分设置为0,在渲染的时候会取出我存在当前像素当中的一个模板缓存值,然后判断它呢是否为一。如果为一的话,才进行渲染,如果不为一,就不不渲染了
- 每一个像素点上,创建了一个这样类似于一个数据缓存,通过它的模板缓存值来判断是否显示,都是像素级别的单位
- 模板还原也要产生一次drawcall,把每个像素点的模板缓存清除
Mask为什么不能合批
因为Mask会产生一个特殊的材质,材质不同就不能合批
public virtual Material GetModifiedMaterial(Material baseMaterial){if (!MaskEnabled())return baseMaterial;var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);if (stencilDepth >= 8){Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);return baseMaterial;}int desiredStencilBit = 1 << stencilDepth;// if we are at the first level...// we want to destroy what is thereif (desiredStencilBit == 1){// StencilMaterial.Add是最主要的方法,这里添加了特殊的材质,因为材质不同,所以不能和mask外的物体合批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;}//otherwise we need to be a bit smarter and set some read / write masksvar maskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit | (desiredStencilBit - 1), StencilOp.Replace, CompareFunction.Equal, m_ShowMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));StencilMaterial.Remove(m_MaskMaterial);m_MaskMaterial = maskMaterial2;graphic.canvasRenderer.hasPopInstruction = true;var unmaskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit - 1, StencilOp.Replace, CompareFunction.Equal, 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));StencilMaterial.Remove(m_UnmaskMaterial);m_UnmaskMaterial = unmaskMaterial2;graphic.canvasRenderer.popMaterialCount = 1;graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);return m_MaskMaterial;}
Mask注意要点
- Mask剔除的部分还是会影响深度计算的,从而影响合批,增加drawcall次数
- Mask剔除的部分还是会drawcall,只不过mask把绘制的像素剔除了
- Mask下的子物体可以正常进行合批
- mask之间只要满足合批条件,那么他们之间的元素也是能够进行合批的
RectMask2D
- RectMask2D本身不占用drawcall
- 因为实际的具体逻辑是在canvas render里进行的,涉及到渲染具体的操作都是内部用c++类做的,性能更好,这里通过性能分析渲染的面和点判断遮盖的部分是否被渲染出来
为什么RecMask2D比Mask性能更好
- RectMask2D本身不占用drawcall,Mask本身有两次drawcall
- RectMask2D对于被遮罩的部分并不会绘制,Mask是把遮罩的部分剔除了
主要代码
public virtual void PerformClipping()
{if (ReferenceEquals(Canvas, null)){return;}//TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)// if the parents are changed// or something similar we// do a recalculate hereif (m_ShouldRecalculateClipRects){MaskUtilities.GetRectMasksForClip(this, m_Clippers);m_ShouldRecalculateClipRects = false;}// get the compound rects from// the clippers that are validbool validRect = true;// 获取需要剪切的区域Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);// If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect// overlaps that of the root canvas.RenderMode renderMode = Canvas.rootCanvas.renderMode;bool maskIsCulled =(renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&!clipRect.Overlaps(rootCanvasRect, true);if (maskIsCulled){// Children are only displayed when inside the mask. If the mask is culled, then the children// inside the mask are also culled. In that situation, we pass an invalid rect to allow callees// to avoid some processing.clipRect = Rect.zero;validRect = false;}if (clipRect != m_LastClipRectCanvasSpace){foreach (IClippable clipTarget in m_ClipTargets){//设置剪切效果clipTarget.SetClipRect(clipRect, validRect);}foreach (MaskableGraphic maskableTarget in m_MaskableTargets){maskableTarget.SetClipRect(clipRect, validRect);maskableTarget.Cull(clipRect, validRect);}}else if (m_ForceClip){foreach (IClippable clipTarget in m_ClipTargets){clipTarget.SetClipRect(clipRect, validRect);}foreach (MaskableGraphic maskableTarget in m_MaskableTargets){maskableTarget.SetClipRect(clipRect, validRect);if (maskableTarget.canvasRenderer.hasMoved)maskableTarget.Cull(clipRect, validRect);}}else{foreach (MaskableGraphic maskableTarget in m_MaskableTargets){//Case 1170399 - hasMoved is not a valid check when animating on pivot of the objectmaskableTarget.Cull(clipRect, validRect);}}m_LastClipRectCanvasSpace = clipRect;m_ForceClip = false;UpdateClipSoftness();
}
RectMask2D注意要点
- 遮罩的部分因为没有绘制,所以不影响深度计算,不影响合批
- 会打断合批,一个RectMask2D下的子物体不可以跟另一个RectMask2D下的子物体进行合批,但RectMask2D下的子物体可以进行合批
- RectMask2D组件的Image之间可以进行合批
根据应用场景选择使用哪个(Mask、RectMask2D)
只需要一个遮罩
选择用RectMask2D性能更好
需要多个遮罩,遮罩下有多个子物体
选择用Mask可以对不同Mask之间的子物体进行合批,这种情况就会比RectMask2D性能更好