UI 血条批次优化

前提介绍

之前的角色血条实现较为简单。画一个血条需要4个批次,有图有真相:


(GPA截取,粉红色为当前drawcall对应的绘制区域)

乍看好像没什么问题,但是游戏是10 vs 10的游戏,极端情况下(两方英雄加小兵进行团P)仅是血条这部分就有上百个批次。虽说图片小、顶点少,但是批次多引起的状态切换开销,大家都懂得。

于是乎,就要想办法合并批次。

之前血条各部分的贴图资源全是分开出的,如果让美术重新出图,程序结构和配置文件都要改。最终美术、程序,策划三方都不爽。
还是发扬优良传统已最小的代价,程序自己把这件事给悄悄做了吧。

因此就有了这篇文章的重点,动态贴图合并算法。

核心算法

把小贴图合并成大贴图也不是什么新鲜事,为了packing lightmaps,图形学前辈就想出了很多绝妙点子。

google “texture atlas”文章一大堆。这里讲一种二叉树算法实现。

思想很简单:递归得把空白大图片切割成小区域,在合适的区域填上小图片。

比如,把图片1填到大图片A的过程:

  • 把A根据图片1的高度上下切割成两部分,上部分B的高度等于图片1的高度。

    高度差大于宽度差的前提下。否则,A切割成左右两部分,左边的宽度等于图片1的宽度。

  • 把B切割成左右两部分。左边的宽度等于图片1 的宽度。

    同上依据宽度差大于高度差(此时高度差为0)

  • 图片1填充到左边部分。
    此时对应的区域结构和数据结构:

继续把图片2填到大图片A:

  • A已被切割,转到B;B已被切割,转到1.

  • 1已被填充,转到1的兄弟节点;空间不够,转到B的兄弟节点

  • 切割B的下部分C,根据宽度差大于高度差,左右切割成两部分。左边部分D的宽度等于图片2的宽度。

  • 继续根据高度差大于宽度差上下切割D节点。上部分的高度等于图片2的高度。

  • 图片2填充到上部分。

此时对应的区域结构和数据结构:


优化成果:

由于层次表现需要,血条被分成了5种: 己方英雄,敌方英雄,己方小兵,敌方小兵,自己。分5个批次画出。
下图为画敌方英雄的批次:

GPA截图一局游戏中同一时刻的某一常规帧进行对比:
批次由1075降为1013

GPU时间比较:

补充说明

为提高插入命中率并高效利用空间,可以事先对所有的小图片进行排序。
但也不一定,比如这次就没做,原因两点:变动和影响尽可能小,小图片数量少压根没必要。
下图是程序运行中,生成的texture cache。


参考文献

http://www.blackpawn.com/texts/lightmaps/

<think>嗯,用户的问题是关于在Unity中处理大量和名字导致DrawCall增加的情况,想要减少DrawCall。首先,我需要回忆一下DrawCall的相关知识。DrawCall是CPU向GPU发送的绘制命令,每个DrawCall处理一个材质和纹理的组合。当场景中有很多不同的材质或纹理时,DrawCall就会增加,可能导致性能问题。 用户提到有大量和名字,并且名字的字体也不一样。这可能意味着每个名字可能使用了不同的字体纹理,导致每个不同的字体都会产生新的DrawCall。此外,可能每个都有独立的UI元素,如果这些没有合并,也会增加DrawCall的数量。 接下来,我需要考虑如何优化。首先想到的是合批处理,包括静态合批和动态合批。但UI元素通常是动态的,所以动态合批可能更相关。但动态合批需要满足一定件,比如相同的材质和纹理。如果和名字使用不同的材质或纹理,动态合批可能无法生效。 对于字体不同的问题,如果每个名字使用不同的字体,那么每个字体都有自己的纹理,这会导致更多的DrawCall。解决办法可能是限制使用字体的数量,或者将常用字符打包到同一个字体图集中,使用位图字体(比如TextMeshPro的Font Asset)。另外,使用系统字体也可能导致问题,因为每个不同的字体样式和大小都会生成新的纹理。 然后是优化通常是简单的UI元素,比如图片或滑块。如果每个都是独立的UI元素,并且使用相同的材质和纹理,可以通过将它们合并到一个画布(Canvas)下来减少DrawCall。但需要注意,如果画布中的元素频繁变化,会导致整个画布的重绘,增加性能开销。可能需要将动态和静态元素分开,使用多个画布。 另外,可以考虑使用GPU Instancing,但UI元素通常不支持这种方式,除非使用自定义着色器。可能更适合3D对象的实例化,比如使用相同的材质和网格。 还有,检查UI元素的叠加顺序,过多的重叠会导致批次中断,无法合批。调整元素的层次结构,减少重叠,可能有助于合批。 对于文本部分,使用TextMeshPro(TMP)可能更高效,因为它支持字体图集,可以将所有需要的字符打包到一个纹理中,减少DrawCall。同时,确保所有文本元素使用相同的字体资产和材质,这样它们可以被合批。 另一个思路是使用对象池,复用和名字的UI元素,减少频繁的创建和销毁,但这更多是优化CPU的性能,而不是直接减少DrawCall。 可能还需要分析当前的DrawCall情况,使用Frame Debugger工具查看每个DrawCall的来源,确定哪些元素导致了额外的DrawCall,然后针对性地优化。 总结可能的优化步骤: 1. 合并使用相同材质和纹理的UI元素到同一个画布下,利用合批。 2. 减少字体种类,使用TextMeshPro并打包字体图集。 3. 将和名字的UI元素设计为使用共享的材质和纹理。 4. 分层次管理画布,减少不必要的重绘。 5. 使用图集打包工具将多个小图合并成大图,减少纹理切换。 6. 避免频繁改变UI元素的属性,如颜色、透明度,以减少批次中断。 7. 对于动态元素,如,可以考虑使用Shader来控制显示,而不是频繁更新UI。 需要验证这些方法是否适用,比如合并画布是否会导致性能问题,或者使用图集是否足够高效。此外,可能还需要测试不同的UI渲染模式,如Overlay和Camera模式对性能的影响。</think>在Unity中处理大量和名字导致DrawCall过高的问题,可通过以下步骤优化: --- ### **1. 核心问题定位** - **字体多样性**:不同字体生成独立纹理,导致材质分离。 - **UI分散**:每个作为独立UI元素,未合理合批。 - **画布管理混乱**:频繁动态更新的元素与静态元素混合,触发全画布重绘。 --- ### **2. 优化策略与实现** #### **(1)字体合并与图集化** - **统一字体**:尽量使用单一字体(如TextMeshPro的`SDF Font Asset`),避免多字体混用。 - **预生成字符集**:在TextMeshPro字体设置中,将常用字符(如字母、数字、符号)打包到同一图集。 ```csharp // 示例:TMP Font Asset设置时勾选 "Include Font Data",预生成字符。 ``` - **禁用动态生成**:关闭`Dynamic Atlas`(Unity UI默认开启),避免运行时生成新字符纹理。 #### **(2)UI合批优化** - **合并材质与纹理**: - 将背景、填充图片合并到同一图集(如使用Sprite Atlas)。 - 所有使用**同一材质**(共享Shader和纹理)。 - **使用MaskableGraphic**: - 通过Shader控制长度,而非缩放UI元素: ```csharp material.SetFloat("_FillAmount", hpPercent); // Shader中使用Clip裁切 ``` - **对象池管理**: - 复用实例,避免频繁Instantiate/Destroy。 ```csharp public class HealthBarPool : MonoBehaviour { [SerializeField] GameObject prefab; Queue<GameObject> pool = new Queue<GameObject>(); public GameObject Get() { if (pool.Count > 0) return pool.Dequeue(); return Instantiate(prefab); } public void Return(GameObject obj) { obj.SetActive(false); pool.Enqueue(obj); } } ``` #### **(3)画布分层与渲染控制** - **分层画布**: - 静态画布(如背景):设为`Static`,仅在初始化时渲染。 - 动态画布(如、名字):独立分离,避免触发全画布重绘。 - **优化渲染模式**: - 优先使用`Screen Space - Overlay`,减少摄像机交互开销。 - 对移动端,可启用`Canvas`的`Additional Shader Channels`减少顶点数据。 #### **(4)DrawCall深度优化** - **禁用不必要的组件**: - 移除UI元素上的`Shadow`、`Outline`等自带效果,改用Shader实现。 - **合批打断检查**: - 使用Unity Profiler或Frame Debugger,定位打断合批的UI元素(如中间插入不同材质的图片)。 --- ### **3. 高级技巧** - **ECS + JobSystem**:对超大规模UI(如MMO中千人同屏),使用ECS架构批量计算位置。 - **GPU-Driven UI**:通过ComputeShader批量提交数据,仅限高阶项目使用。 --- ### **4. 验证工具** - **Frame Debugger**:逐帧查看DrawCall来源。 - **Unity Profiler**:监控`Canvas.SendWillRenderCanvases`耗时。 --- 通过上述方法,可有效将和名字的DrawCall从数百降至个位数。实际效果需结合项目具体结构测试调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值