Unity UGUI Batches合批规则详解

在处理UGUI DrawCall问题的时候,我们经常遇到各式各样的问题。

问题1:在处理UGUI合批的时候,发现了一个面板父节点发生旋转,底下的UI合批顺序会被打乱。

更多问题见知乎:UGUI 3D界面/Z轴位移 分批失效的处理方法

因为看不到UGUI Batches的源码,所以就一直不清楚它的合批规则到底是怎样的。所以今天就来一个一个问题解答,顺序详解一下它的源码:

对于Z轴旋转导致无法合批,底层逻辑是这样的:
判断控件世界坐标包围盒z轴是否都为0,不为0则独占一个depth。接着进行排序,深度是第一优先级排序、再到材质、贴图等。

那么到底具体是怎样?我来总结一下:

  1. 先获得一个按Hierarchy的顺序的列表
  2. 计算每个物体的深度。
    2.1 深度从0开始递增。如果世界包围盒Z轴不为0(或isCanvasInjectionIndex),则需独占一个批次,同时独占一个深度。即等于之前所有物体最大深度+1,后一个物体深度需要+1。
    2.2 对一般物体的深度。会判断是否可跟之前的物件共享深度,走接下来的流程。
    2.2.1 先按格子(默认大小是120,根据包围盒再计算)划分出多个格子。(只是为了加速求交)。
    2.2.2 计算物体包围相交哪些格子,再跟格子中已有的物体进行包围盒相交判断。如果不相交则使用当前深度;如果相交且可合批,则使用相交物体中最大的深度;如果相交且不可合批,则使用相交物体中最大的深度+1。合批条件:无独占批次+材质相同+贴图相同+裁剪开关和裁剪矩形相同+贴图A8格式一致(kTexFormatAlpha8)
    2.2.3 将该物体加入所有相交的格子中。若遇到独占深度的物体,则格子数据清空。即后续物件不跟之前的物件共享深度。
  3. 排序:按照深度->材质->贴图->层级顺序优先级排序。
  4. 合批:对排序后的列表,从头开始一个一个检测是否能与前面的物体合批。合批条件:无独占批次(只判断isCanvasInjectionIndex)+材质相同+贴图相同+裁剪开关和裁剪矩形相同+贴图A8格式一致(kTexFormatAlpha8),非SubBatch只判断前两个条件,一般情况下UI的材质都一样。

源码剖析:

在ui合批的时候,会先给按照depth(ui的层数)以及materialInstanceID,textureID进行排序,如下调用过程如下:
SortForBatching先对每一个ui进行数据准备,调用PrepareDepthEntries();

void SortForBatching(const RenderableUIInstruction* inputInstructions, UInt32 count, RenderableUIInstruction* outputInstructions, int sortBucketGridSize)
{PROFILER_AUTO(gSort, NULL);// Create depth blockdynamic_array<DepthSortEntry> depthEntries(kMemTempAlloc);depthEntries.resize_uninitialized(count);PrepareDepthEntries(inputInstructions, count, depthEntries.data(), sortBucketGridSize);std::sort(depthEntries.data(), depthEntries.data() + count);// Generate new sorted UIInstruction arrayfor (UInt32 i = 0; i < count; i++){Assert(depthEntries[i].depth >= 0);const RenderableUIInstruction& instruction = inputInstructions[depthEntries[i].renderIndex];outputInstructions[i] = instruction;}
}

在PrepareDepthEntries时候:

static void PrepareDepthEntries(const RenderableUIInstruction* uiInstruction, UInt32 count, DepthSortEntry* output, int sortBucketGridSize)
{if (count == 0)return;DepthSortGrid grid;grid.Initialize(0);int maxDepth = 0;for (int i = 0; i < count; ++i){// if we have a forced new batchif (SortingForceNewBatch(uiInstruction[i]) != NoBreaking){// try for a run of forced batchesint forcedCount = 0;for (int j = i; j < count; ++j){// TODO: If this is true through the end the instructions then we never increment i and// can get into a very long infinite loop as we never hit the i += forcedCount - 1if (SortingForceNewBatch(uiInstruction[j]) != NoBreaking){// just set the depth to be the// maxDepth +1++forcedCount;output[j].renderIndex = uiInstruction[j].renderDepth;output[j].depth = ++maxDepth;continue;}// we have run out of forced depths...// create a new grid for the next set of// batchable elementsAssert(forcedCount > 0);i += forcedCount - 1;grid.Initialize(++maxDepth);break;}}else{int depth = grid.AddAndGetDepthFor(uiInstruction[i], uiInstruction, sortBucketGridSize);maxDepth = std::max(depth, maxDepth);output[i].renderIndex = uiInstruction[i].renderDepth;output[i].depth = depth;output[i].materialInstanceID = uiInstruction[i].materialInstance.GetInstanceID();output[i].textureID = uiInstruction[i].textureID;output[i].texelSize = uiInstruction[i].texelSize;}}
}

有一个函数,SortingForceNewBatch(),这个函数会在ui与canvas不共面,或者不是同一个canvas的时候,强制新启一个batch,同时把深度增加。也就是下面这个函数的判断:

inline BatchBreakingReason SortingForceNewBatch(const RenderableUIInstruction& instruction)
{if (!instruction.isCoplanarWithCanvas)return NotCoplanarWithCanvas;if (instruction.isCanvasInjectionIndex)return CanvasInjectionIndex;return NoBreaking;
}

排序方法如下:

static bool operator<(const DepthSortEntry& a, const DepthSortEntry& b)
{// first sort by depthsif (a.depth != b.depth)return a.depth < b.depth;// if they're equal, sort by materialsif (a.materialInstanceID != b.materialInstanceID)return a.materialInstanceID < b.materialInstanceID;// if they're equal, sort by texturesif (a.textureID != b.textureID)return a.textureID < b.textureID;//TODO: we could break 'fast' batching here due// to not looking at rect clipping / what the clip// rect is. This is unlikely (due to the next step// being render order), but it's something to// investigate at a later time.// all else being equal... sort by render orderreturn a.renderIndex < b.renderIndex;
}

我们ui只用了一个canvas,显然,是因为不共面导致的,因此通过调试,发现isCoplanarWithCanvas为false的计算是通过相对于canvas的Z值确定的:

void DoSyncWorldRect(UIInstruction& uiData)
{MinMaxAABB worldBounds;TransformAABBSlow(uiData.localBounds, uiData.transform, worldBounds);ProjectAABBToRect(worldBounds, uiData.globalRect);uiData.worldBounds = worldBounds;uiData.isCoplanarWithCanvas = CompareApproximately(worldBounds.m_Min.z, 0.0f, 0.001F) && CompareApproximately(worldBounds.m_Max.z, 0.0f, 0.001F);uiData.dirtyTypesFlag = kTypeOrder;
}

可以看到,是通过判断worldBounds.m_Max.z worldBounds.m_Min.z是否在0.001f范围内,而这个z是通过旋转影响的,我通过调试发现,在PlaneDistance比较大的时候这个误差比较小,会小于0.001,然而PlaneDistance比较小的时候这个值会大于0.001,我们设置1的时候这个z会达到0.019左右,因此我将0.001改成0.01,发现怎么转向都没问题了。因为转不同的方向不同的ui旋转的计算值的误差不一样,这也就是为什么转不一样的方向drawcall不一样的原因了。

因此大家在优化drawcall的时候一定要注意,防止这种情况的出现,这种情况会导致depth非常大,很多ui都会成为单独的批次。这也算unity的一个坑吧。毕竟z值相差较大也去合批容易造成渲染顺序不正确的问题。

Unity UGUI Batches合批规则详解(含源码)相关推荐

  1. Excel一元线性回归示例与演算步骤详解含源码及注释

    Excel一元线性回归示例 1 声明 本文的数据来自网络,部分代码也有所参照,这里做了注释和延伸,旨在技术交流,如有冒犯之处请联系博主及时处理. 2 一元线性回归简介 回归分析只涉及到两个变量(Y因变 ...

  2. 详解 Python 源码之对象机制

    在Python中,对象就是在堆上申请的结构体,对象不能是被静态初始化的,并且也不能是在栈空间上生存的.唯一的例外就是类型对象(type object),Python中所有的类型对象都是被静态初始化的. ...

  3. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

  4. hadoop作业初始化过程详解(源码分析第三篇)

    (一)概述 我们在上一篇blog已经详细的分析了一个作业从用户输入提交命令到到达JobTracker之前的各个过程.在作业到达JobTracker之后初始化之前,JobTracker会通过submit ...

  5. SpringMVC异常处理机制详解[附带源码分析]

    SpringMVC异常处理机制详解[附带源码分析] 参考文章: (1)SpringMVC异常处理机制详解[附带源码分析] (2)https://www.cnblogs.com/fangjian0423 ...

  6. 详解LAMP源码编译安装

    实战:LAMP源码编译安装 家住海边喜欢浪:zhang789.blog.51cto.com 目录 详解LAMP源码编译安装 LAMP简介 一.准备工作 二.编译安装 Apache 三.编译安装 MyS ...

  7. spark RDD详解及源码分析

    spark RDD详解及源码分析 @(SPARK)[spark] spark RDD详解及源码分析 一基础 一什么是RDD 二RDD的适用范围 三一些特性 四RDD的创建 1由一个已经存在的scala ...

  8. spark 调度模块详解及源码分析

    spark 调度模块详解及源码分析 @(SPARK)[spark] spark 调度模块详解及源码分析 一概述 一三个主要的类 1class DAGScheduler 2trait TaskSched ...

  9. FPGA学习之路—接口(2)—I2C协议详解+Verilog源码分析

    FPGA学习之路--I2C协议详解+Verilog源码分析 定义 I2C Bus(Inter-Integrated Circuit Bus) 最早是由Philips半导体(现被NXP收购)开发的两线时 ...

最新文章

  1. AI将成科学家“高级定制”工具
  2. iOS Swift编程语言
  3. UVA10020(最小区间覆盖)
  4. SBO用户清理(最近一直未登录过用户)
  5. 点云三角化之后还能贴图嘛_雪糕化了之后重新冷冻还能吃吗?宁波这个实验真相了!...
  6. POJ3244(工科数学分析)
  7. Java EE 8 MVC:使用表单参数
  8. 分享25个优秀的网站底部设计案例
  9. printf输出字符串_c语言入门 第十二章 字符串
  10. MooTools 1.4 源码分析 - (关于Core、Type等模块分析)
  11. 关于翻译书籍版权的讨论
  12. 移动网流量用户身份识别系统的源代码_真武庙车辆识别系统安装效果图
  13. 2022 WTM 女性开发者大会邀你开启心旅程
  14. android psensor测试,android传感器Gsensor和Psensor的使用举例
  15. 3D着色器(OpenGL)
  16. 【计算机网络 12】5G消息能取代IM,Java理论知识总结
  17. 迅雷下载器-FDM,看2019新年大电影
  18. xpath解析最全攻略
  19. Node.js(三)路由器、中间件、MySQL模块、RESTful接口
  20. 各大高校自曝状态一览 排名不分先后

热门文章

  1. 详细设计说明书--文档模板
  2. 求无限循环小数的循环节
  3. 高性能本地缓存Ristretto(二)——过期策略
  4. feof()函数的文件操作
  5. Strusts2简单入门教程
  6. 让div在body中居中显示
  7. 利用Python实现自动批量图片格式转换
  8. 使用xiaopiu常见技巧
  9. Win11远程桌面连接怎么打开?Win11远程桌面连接的五种方法
  10. 外企人常使用的工作邮箱,建议收藏!