Draw call batching 绘制调用(DC)合批 性能系列2

本文详细介绍了Unity中的两种合批技术——动态合批和静态合批,以优化绘制调用(DC)并提升性能。动态合批适用于移动游戏对象,而静态合批适合静态对象且能节省更多CPU资源,但会增加内存开销。合批的关键在于共享材质,同时,文章也提醒注意合批的限制和注意事项,如顶点数量、Shader类型等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Draw call batching 绘制调用(DC)合批

本文档主要是对Unity官方手册的个人理解与总结(其实以翻译记录为主:>)
仅作为个人学习使用,不得作为商业用途,欢迎转载,并请注明出处。
文章中涉及到的操作都是基于Unity2018.3版本
参考链接:https://docs.unity3d.com/Manual/DrawCallBatching.html

To draw a GameObject on the screen, the engine has to issue a draw call to the graphics API (such as OpenGL or Direct3D). Draw calls are often resource-intensive, with the graphics API doing significant work for every draw call, causing performance overhead on the CPU side. This is mostly caused by the state changes done between the draw calls (such as switching to a different Material), which causes resource-intensive validation and translation steps in the graphics driver.
在屏幕上绘制GameObject,引擎必须对图形API(如OpenGL或Direct3D)发出DC。DC通常是资源密集型的,图形API为每次绘制调用做大量工作,导致CPU方面的性能开销。这主要是由于DC之间所做的状态更改(例如切换到不同的材质)造成的,这会导致图形驱动程序中的资源密集型验证和转换工作。

Unity uses two techniques to address this:
Unity使用了两个技术来解决这个问题:

  • Dynamic batching: for small enough Meshes, this transforms their vertices on the CPU, groups many similar vertices together, and draws them all in one go.
    动态合批:对于足够小的网格,把CPU上相似的顶点转移到一起,并一次性将它们全部绘制出来。

  • Static batching: combines static (not moving) GameObjects into big Meshes, and renders them in a faster way.
    静态合批:将静态(不移动)游戏对象组合成大的网格,并以更快的方式渲染它们。

Built-in batching has several benefits compared to manually merging GameObjects together; most notably, GameObjects can still be culled individually. However, it also has some downsides; static batching incurs memory and storage overhead, and dynamic batching incurs some CPU overhead.
与手动合并游戏对象相比,内置合批有很多好处;最值得注意的是,游戏对象仍然可以单独剔除。然而,它也有一些缺点;静态合批会导致内存和存储开销,而动态合批会导致一些CPU开销。

Note: Dynamic batching is not compatible with graphics jobs (see Player Settings). If graphics jobs are enabled, dynamic batching are disabled in Standalone builds.
注意:动态合批与graphics jobs不兼容(参见Player设置)。如果启用了graphics jobs,则在Standalone构建中禁用动态

<think>我们正在讨论Unity中滑动列表的优化,特别是减少Draw Calls(DC)。DrawCalls是CPU向GPU发送的绘制命令,每次调用都会产生一定的开销。减少DrawCalls可以显著提升游戏性能,特别是在移动设备上。根据引用内容,我们有一些关于UI排序和渲染队列的代码片段。但用户的问题更侧重于滑动列表的优化。滑动列表通常包含大量重复的UI元素(如物品标、文本等),这些元素如果处理不当,会导致DrawCalls激增。###核心优化策略1.**Batching)**:-Unity的UI系统(如UGUI)会自动对符条件的UI元素进行,将多个元素并到一个Draw Call中。-条件包括:使用相同的纹理、相同的材质,并且没有中间穿插其他材质。因此,要确保列表中的元素尽可能使用相同的图集(Atlas)。2.**图集(Atlas)管理**:-将滑动列表中的所有标、背景等片打包到一个图集中。这样,所有使用该图集的UI元素就可以被。-注意避免在图集中穿插使用其他片,否则会打断。3.**避免重叠和穿插**:-如果不同深度的UI元素重叠,或者中间穿插了其他材质的元素(例如一个使用图集A,下一个使用图集B),会打断。-确保滑动列表中的元素在层级上连续,并且使用相同的材质。 4. **动态处理与静态处理**:-对于静态不变的列表元素,可以考虑使用静态处理(但UI元素通常是动态的)。-动态处理对于网格较小的UI元素有效,但Unity对动态处理有较多限制(顶点数等)。 5. **使用对象池(Object Pooling)**:-滑动列表通常只显示部分项(比如10项),但可能包含大量数据。通过对象池复用UI项,避免频繁创建和销毁,从而减少GC和重建网格的开销。 6. **减少Canvas的重绘**:- UGUI中,Canvas负责生成网格。当Canvas内的元素发生变化时,会触发重绘(Rebuild)。频繁重绘会导致性能问题。-将滑动列表放在单独的Canvas中,避免其他UI元素变化导致整个列表重绘。-使用`CanvasGroup`来控制一组元素的显示和隐藏,而不是单独操作,因为单独操作每个元素可能导致多次重绘。 7. **优化布局计算**:-避免使用复杂的布局组(LayoutGroup),尤其是在运行时动态添加/删除项。考虑预先计算位置,使用自定义脚本来控制布局。8.**引用中提到的UI排序问题**:-引用[2]中的代码通过调整渲染队列(renderQueue)来控制UI和特效的显示顺序。这可以用于确保滑动列表中的元素正确排序,避免因为渲染顺序错误导致额外的Draw Calls。 ###具体实施步骤1.**创建图集**:-使用Unity的Sprite Atlas(UGUI)功能,将滑动列表所需的片打包成图集2.**设计UI项模板**:-设计一个预制体(Prefab)作为列表项,确保它只使用图集中的片,并且材质相同。 3. **对象池实现**:-初始化时创建一定数量的UI项(比如可见项的两倍),并隐藏多余的项。-当滑动时,复用隐藏的项来显示新的数据,而不是创建新项。 4. **控制Canvas重绘**:-将滑动列表放在一个单独的Canvas下。-在更新数据时,尽量量修改UI项的属性(如文本、片),然后手动调用`Canvas.ForceUpdateCanvases()`(谨慎使用,因为它会立即重绘)或者设计成在帧末统一更新。 5. **使用代码控制布局**:-避免使用`VerticalLayoutGroup`或`HorizontalLayoutGroup`,因为它们会在每次改变子物体时重新计算布局。可以自己计算位置,通过设置`RectTransform`的位置。6.**渲染队列调整(如果需要)**:-如果滑动列表中有特效等需要穿插在UI中,可以参考引用[2]的方法,通过脚本控制渲染队列,确保不会打断。###示例代码(对象池简化版)```csharpusing UnityEngine; usingSystem.Collections.Generic;public classScrollListOptimizer :MonoBehaviour{public GameObjectitemPrefab;//列表项预制体public intpoolSize=20;//对象池大小public RectTransform content; //滑动列表的Contentprivate List<GameObject>itemPool= newList<GameObject>();void Start(){//初始化对象池for(inti =0; i< poolSize;i++){GameObjectitem= Instantiate(itemPrefab, content);item.SetActive(false);itemPool.Add(item);}}//更新列表数据publicvoid UpdateList(List<ItemData> dataList){//先隐藏所有项foreach (varitem initemPool)item.SetActive(false);//显示需要的项,并更新数据for(inti =0;i <dataList.Count;i++){if(i>= itemPool.Count)break;//或者动态扩展池GameObject item= itemPool[i];item.SetActive(true);//更新item的数据显示,例如://item.GetComponent<ItemScript>().SetData(dataList[i]);//设置位置item.GetComponent<RectTransform>().anchoredPosition= newVector2(0, -i *100); //假设每个项高度100}//更新Content的高度content.sizeDelta =new Vector2(content.sizeDelta.x,dataList.Count *100);} }``` ###注意事项- **图集大小限制**:移动设备上图集大小不要超过2048x2048,避免内存和性能问题。 -**动静分离**:将频繁变化的UI(如滚动列表)和静态UI(如背景)放在不同的Canvas中。- **避免Mask组件的过度使用**:滚动区域通常使用`ScrollRect`和`Mask`,但`Mask`会生成额外的DrawCall(模板测试)。如果可能,使用`RectMask2D`代替,因为它更高效。 ###引用说明[^1]提到排列item到格子中,这类似于网格布局的滑动列表,但未直接涉及DrawCall优化。[^2]提供了通过调整渲染队列来控制UI和特效显示顺序的方法,可用于确保滑动列表中的元素正确排序,避免因渲染顺序导致失败。 通过以上措施,可以显著减少滑动列表的DrawCalls,提升性能。 §§相关问题§§1.如何在Unity中创建和使用SpriteAtlas?2.对象池(Object Pooling)在Unity中的实现细节有哪些?3.UGUI中Canvas的重绘机制是怎样的?如何避免频繁重绘? 4.除了滑动列表,还有哪些常见的UI优化技巧? 5. Unity中的Draw Call和SetPassCall有什么区别?</think>### Unity滑动列表优化Draw Calls指南 在Unity中优化滑动列表的Draw Calls(DC)是提升UI性能的关键。以下是系统化的解决方案: #### 核心优化策略 1. **图集(Atlas)整** - 将所有UI元素(标/文字/背景)打包到**单个图集**中 - 使用`Sprite Packer`自动并:`Window > 2D > Sprite Packer` - **效果**:相同图集的元素可并到1个Draw Call 2. **动态处理配置** ```csharp // 在UI元素的材质上启用动态处理 material.enableInstancing = true; ``` - 需满足条件: - 相同材质和纹理 - 网格顶点数<900 - 无缩放负值 3. **层级深度优化** - 使用**UIPanel层级管理**(参考引用[2]): ```csharp public class UISortBehavior : MonoBehaviour { public UIPanel panel; public void UpdateSortUI() { // 调整渲染队列避免层级穿插 material.renderQueue = widgetInFrontOfMe.drawCall.renderQueue + 1; } } ``` - **关键点**:确保连续列表项的渲染队列连续,避免层级穿插打断 4. **对象池技术** ```csharp // 对象池基础实现 List<GameObject> itemPool = new List<GameObject>(); void CreatePool(int count) { for(int i=0; i<count; i++){ GameObject item = Instantiate(itemPrefab); item.SetActive(false); itemPool.Add(item); } } GameObject GetItem() { foreach(GameObject item in itemPool) { if(!item.activeSelf) return item; } return ExpandPool(); } ``` - **优势**:避免滑动时频繁实例化/销毁导致的DC波动 5. **Canvas分区策略** - 将**滚动区域**与**静态UI**分离到不同Canvas - 配置: - 滚动列表 → `Dynamic Canvas`(仅滚动时重绘) - 固定元素 → `Static Canvas`(不重绘) - 使用`CanvasGroup`控制局部重绘 #### 进阶技巧 1. **Mask优化替代方案** - 避免直接使用`Mask`组件(增加2DC) - 改用`RectMask2D`(UGUI 2017+版本) - 或使用**自定义Shader**实现裁剪效果 2. **数据分帧加载** ```csharp IEnumerator LoadItemsCoroutine(List<Data> dataList) { for(int i=0; i<dataList.Count; i++){ if(i%5 == 0) yield return null; // 每5项暂停一帧 UpdateItem(GetItem(), dataList[i]); } } ``` - 避免单帧突发大量DC 3. **LOD细节分级** - 快速滚动时显示**简化版UI**(纯色块+文字) - 滚动停止后加载**完整版UI** - 使用`ScrollRect.onValueChanged`监听滚动速度 #### 性能检测工具 1. **Frame Debugger**:`Window > Analysis > Frame Debugger` 2. **Profiler**:查看`UI.Render`耗时 3. **内置计数器**:`Stats`面板查看实时DC数量 > **优化效果对比**: > | 优化前 | 优化后 | > |---|---| > | 50项列表约25-35 DC | 可降至3-5 DC | > | 滚动卡顿明显 | 帧率稳定60fps | #### 引用说明 引用[2]中的`UISortBehavior`类演示了通过控制渲染队列深度来优化的方法,这对滑动列表的层级管理至关重要[^2]。引用[1]提到的网格排列逻辑可结对象池实现高效布局[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值