Unity UGUI 规范 优化 大全
精要:
Layout:布局组件,控制RectTransform位置大小
Graphic:渲染组件,如image,text
Batch:把符合规则的UI元素集合起来,一次性渲染,规则是材质相同,层级中间没有不同材质,Batch后会缓存,如果UI元素没变化,可快速完成渲染,有变化需要走rebuild
Canvas:画布,画布内的UI有Batch关系,Canvas之间没有
CanvasUpdateRegistry: 检测Graphic与Layout的更新,dirty时
Rebuild: Layout更新(需要排序,从下到上),Graphic更新(不需排序,顶点数据有变化,要重建网格;材质数据有变化,Render更新
Profile测试,常见问题点:
ClipperRegistry.Cull: 更新RectMask2D下面UI的裁剪,一般情况当ScrollRect比较多时,开销较高
Canvas.SendWillRenderCanvases: UI元素变化,引起rebuild,如果下边Layout高,说明位置大小发生了变化;Render高,说明材质发生了变化
Canvas.SendWillRenderCanvases持续高,UI里有动态元素,引起频繁重建
Canvas.BuildBatch或UpdateBatches高,Canvas上的Render太多
UI Render开销高,大概率是OverDraw导致的
WillRenderCanvas中IndexedSet_Sort或SortLayoutList时间高,应该是Layout组件数量多
Text_OnPopulate,可能与开了Best Fit有关
Outline_ModifyMesh,Shadow_ModifyMesh,与字体的描边,阴影有关
优化方法:
全屏UI显示时,禁用3D Camera,或者用一个快照做假
不需要Render,但需要有raycast的UI,去掉Render
动静UI分离,让rebuild范围最小化
- 同一个UI界面的图片尽可能放到一个图集中,这样可以尽可能的降低drawcall
- 共用的图片放到一个或几共享的图集中,例如通用的弹框和按钮等;相同功能的图片放到一个图集中, 例如装备图标和英雄头像等;这样可以降低切换界面的加载速度
- 不同格式的图片分别放到不同的图集中,例如透明(带Alpha)和不透明(不带Alpha)的图片,这样可以减少图片的存储空间和占用内存。(UGUI的sprite packer会自动处理这种情况)???这样会不会经常打断渲染的顺序,增加批次
- 单独场景内的UI与其他场景的UI不要混用(比如,战斗、英雄场景)
- 图片分辨率能降就降,在不太影响UI表现的前提下
- 图片格式要用ASTC,除非有特殊要求的图片
- 及时删除UI中的失效节点、脚本、动画,还有一些Disable的废弃内容,这些内容会影响加载时间与内存占用
- 减少Rebuild的频率,将动态UI元素(频繁改变例如顶点、alpha、坐标和大小等的元素)与静态UI元素分离出来,放到特定的Canvas中
- 使用尽可能少的UI元素;在制作UI时,一定要仔细查检UI层级,删除不不必要的UI元素,这样可以减少深度排序的时间,以及Rebuild的时间
- 谨慎使用UI元素的enable与disable,因为它们会触发耗时较高的rebuild,替代方案之一是enable和disableUI元素的canvasrender或者Canvas
- Text的Best Fit选项尽量不要用,虽然这个选项可以动态的调整字体大小以适应UI布局而不会超框,但其代价是很高的,Unity会为用到的该元素所用到的所有字号生成图元保存在atlas里,不但增加额外的生成时间,还会使得字体对应的atlas变大。
- 描边,字体阴影尽量不要用,用的话推荐用TextMeshPro的
- 少用Canvas的Pixel Perfect选项,该选项会使得ui元素在发生位置变化时,造成layout Rebuild。(比如ScrollRect滚动时,如果开启了Canvas的pixel Perfect,会使得Canvas.SendWillRenderCanvas消耗较高)
- 使用缓存池来保存ScrollView中的Item,对于移出或移进View外的的元素,不要调用disable或enable,而是把它们放到缓存池里或从缓存池中取出复用
- 不要使用空的Image,在Unity中,RayCast使用Graphi作为基本元素来检测touch,在笔者参与的项目中,很多同学使用空的image并将alpha设置为0来接收touch事件,这样会产生不必要的overdraw。如果需要用,请用Block
- 少用引起频繁Rebuild的功能组件:Layout
- 该过程由CanvasUpdateRegistry监听Canvas的WillRenderCanvases(上图中1)而执行,主要是对前标记为dirty的layout和craphic执行rebuild。引起layout和graphic的dirty主要原因是因为Canvas树形结构下的UI元素发生了变化(例如增加删除UI对象,UI元素的顶点,rec尺寸改变等)调用了Graphic.SetDirty(实际上最终都会调用CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild)。
- 在rebuild layout之前会对Layout rebuild queue中的元素依据它们在heiarchy中的层次深度进行排序(上图中的2),排列的结果是越靠近根的节点越会被优先处理。
- rebuild layout(上图中的3),主要是执行ILayoutElement和ILayoutController接口中的方法来计算位置,Rect的大小等布局信息。
- rebulid graphic(上图中的4),主要是调用UpdateGeometry重建网格的顶点数据(上图中5)以及调用UpdateMeterial更新CanvasRender的材质信息(上图中6)。
- a) 遍历所有UI元素(已深度优先排序),对当前每一个UI元素CurrentUI,如果不渲染,CurrentUI.depth = -1,如果渲染该UI且底下没有其他UI元素与其相交(rect Intersects),其CurrentUI.depth = 0;
- b) 如果底下有一个的需要渲染的UI元素LowerUI与CurrentUI相交的情况下,且
- 可以Batch(material instance id 和 texture instance id 相同),depth_i = LowerUI.depth;
- 不可以Batch,depth _i= LowerUI.depth + 1;
- 如果底下有n个UI元素与CurrentUI相交,根据b)计算n个的depth_i取最大的作为CurrentUI的depth: CurrentUI.depth = Max( depth _1, depth _2, depth _3 ... , depth _n)。
- Depth计算完后,根据Depth排序UI Instructions,如果Depth相等,依次根据材质ID、texture ID、渲染顺序(即UI层级队列顺序)排序,剔除depth == -1的UI元素,得到Batch前的UI 元素队列VisiableList。
- 对VisiableList中相邻且可以Batch(相同material和texture等)的UI元素合并批次,然后再生成相应mesh数据进行绘制。
- 注意:在Depth计算算法中,由于要遍历所有UI元素和已计算的底层UI元素(平方复杂度),源码中使用分组计算包围盒矩形的方法加快计算,即16个UI元素为一组计算Group Rect,检查是否与底层UI元素相交时,先计算是否与底层Group相交,如果相交再与Group中的元素做判定。
- 因此,UI元素数目过多和层次结构过于复杂,会影响排序和Batch更新速度,合理规划UI元素数量和层次结构可以提高UI性能。