drawcall优化

unity3D 对于移动平台的支持无可厚非,但是也有时候用Unity3D 开发出来的应用、游戏在移动终端上的运行有着明显的效率问题,比如卡、画质等各种问题。自己在做游戏开发的时候偶有所得。对于主要影响性能的因素做个总结。

主要因素有:

        1.      Saved by batching 值过大   ---- > 这个值主要是针对Mesh的批处理,这个值越高,应用就越卡   

        2.     Drawcall 值过大 ---- >  Drawcall 值过大,所需要的 GPU 的处理性能较高,从而导致CPU的计算时间过长,于是就卡了

        3.     点、面过多           ---- > 点、面过多,GPU 根据不同面的效果展开计算,并且CPU计算的数据也多,所以效果出来了,但是卡巴斯基

由于 Saved by batching 和 Drawcall 值过大所引起的卡的问题我所做的优化方式有:

        1.    对于模型 :Mesh 合并,有个不错的插件(DrawCall Minimizer   --->  直接上Asset Store 下载即可,免费的,而且有文档,很容易上手)

        2.    对于UI  :  尽量避免使用Unity3D自带的 GUI 换用 NGUI或者EZGUI;因为这两个UI插件对于UI中的图片处理是将UI图片放置在一个 Atlas 中,一个 Atlas 对应一个Drawcall

        3.   对于灯光: 可以使用 Unity3D 自带的  Lightmapping 插件来烘焙场景中的灯光效果到物体材质上

        4.  对于场景: 可以使用 Unity3D 自带的 Occlusion Culling 插件把静止不动的场景元素烘焙出来

        4.   对于特效:尽量把材质纹理合并

对于Unity3D 在移动终端上支持的Drawcall 数到底多少,主要是跟机子性能有关的,当然也不是说值小性能就一定没问题(本人亲测,也有17就卡的,主要是模型材质纹理过大所引起的),目前我做的是70左右的,还OK,挺正常的

 

由于点、面过多所导致的性能问题,最好用简模,用四面体来做复杂的模型,但是面、点也别太多,至于Unity3D 到底支持多少点、面的说法各异,我也搞不懂,总之少些肯定OK

 


检测方式:

一,Unity3D 渲染统计窗口

Game视窗的Stats去查看渲染统计的信息:

1、FPS

fps其实就是 frames per second,也就是每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

 

2、Draw calls

batching之后渲染mesh的数量,和当前渲染到的网格的材质球数量有关。

 

3、Saved by batching

渲染的批处理数量,这是引擎将多个对象的绘制进行合并从而减少GPU的开销;

很多GUI插件的一个好处就是合并多个对象的渲染,从而降低DrawCalls ,保证游戏帧数。

 

4、Tris 当前绘制的三角面数

 

5、Verts 当前绘制的顶点数

 

6、Used Textures 当前帧用于渲染的图片占用内存大小

 

7、Render Textures 渲染的图片占用内存大小,也就是当然渲染的物体的材质上的纹理总内存占用

 

8、VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存

 

9、VBO Total 渲染过程中上载到图形卡的网格的数量,这里注意一点就是缩放的物体可能需要额外的开销。

 

10、Visible Skinned Meshes 蒙皮网格的渲染数量

 

11、Animations 播放动画的数量

注意事项:

1,运行时尽量减少 Tris 和 Draw Calls

预览的时候,可点开 Stats,查看图形渲染的开销情况。特别注意 Tris 和 Draw Calls 这两个参数。

一般来说,要做到:

Tris 保持在 7.5k 以下,有待考证。

Draw Calls 保持在 20 以下,有待考证。

2,FPS,每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

3,Render Textures 渲染的图片占用内存大小。

4,VRAM
usage 显存的使用情况,VRAM总大小取决于你的显卡的显存。

 

二,代码优化

1. 尽量避免每帧处理

比如:

function Update() { DoSomeThing(); }

可改为每5帧处理一次:

function Update() { if(Time.frameCount % 5 == 0) { DoSomeThing(); } }

2. 定时重复处理用 InvokeRepeating 函数实现

比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:

 

function Start() { InvokeRepeating("DoSomeThing", 0.5, 1.0); }

 

3. 优化 Update, FixedUpdate, LateUpdate 等每帧处理的函数

函数里面的变量尽量在头部声明。

比如:

function Update() { var pos: Vector3 = transform.position; }

可改为

private var pos: Vector3; function Update(){ pos = transform.position; }

 

4. 主动回收垃圾

给某个 GameObject 绑上以下的代码:

function Update() { if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }

 

5. 优化数学计算

比如,如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等

 

6,减少固定增量时间

将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。

7,减少GetComponent的调用

使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为"缓存"的引用)。例如,如果您使用如下的代码:

function Update () {

transform.Translate(0, 1, 0);

 

}

通过下面的更改您将获得更好的性能:
var myTransform : Transform;

function Awake () {

myTransform = transform;

}

function Update () {

myTransform.Translate(0, 1, 0);

}


8,避免分配内存

您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于"传值"操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。

 
9,使用iOS脚本调用优化功能

UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。从Mono的脚本调用 C/C++函数也存在着一定的性能开销。您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。此设置的选项有:

Slow and Safe – Mono内部默认的处理异常的调用


Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。不过,它并不支持异常,因此应谨慎使用。它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。

 

10,

优化垃圾回收


如上文所述,您应该尽量避免分配操作。但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用:

如果堆比较小,则进行快速而频繁的垃圾回收

这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:
if (Time.frameCount % 30 == 0)

{

System.GC.Collect();

}


但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间

如果堆比较大,则进行缓慢且不频繁的垃圾回收

这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大。因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的"无用"对象):

function Start() {


var tmp = new System.Object[1024];

// make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks

for (var i : int = 0; i < 1024; i++)

tmp[i] = new byte[1024];

// release reference

tmp = null;

}

游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:
System.GC.Collect();

另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。

 

三,模型

1,压缩 Mesh

导入 3D 模型之后,在不影响显示效果的前提下,最好打开 Mesh Compression。

Off, Low, Medium, High 这几个选项,可酌情选取。

2,避免大量使用 Unity 自带的 Sphere 等内建 Mesh

Unity 内建的 Mesh,多边形的数量比较大,如果物体不要求特别圆滑,可导入其他的简单3D模型代替。

原文地址:http://www.xuebuyuan.com/2160665.html
<think>嗯,用户问的是Cocos Label的DrawCall优化。首先,我需要回忆一下Cocos Creator中Label组件的工作原理。Label通常用于显示文本,但不同的字体类型(系统字体、BMFont、TTF)可能会有不同的渲染方式,进而影响DrawCall的数量。 DrawCall是CPU准备数据并通知GPU渲染的命令,次数过多会导致性能问题。所以优化Label的DrawCall主要在于减少次数和合并批次。首先,我应该考虑字体的选择。系统字体可能每个Label都是一个单独的DrawCall,而BMFont和TTF如果使用相同的字体和材质,可能可以合批。 接下来,合批的条件是关键。比如,相同图集、相同材质、相同的渲染状态。用户可能需要知道如何设置这些条件,比如确保多个Label使用同一个字体文件,并且没有穿插其他渲染组件打断合批。 动态文本的问题,比如数字频繁变化,可能会导致图集更新,增加DrawCall。这时候可能需要用缓存或者位图字体来优化。另外,Label节点的层级和渲染顺序也会影响合批,如果不同Label之间有其他类型的节点,可能会打断合批,所以需要调整节点顺序。 还有,使用文本内容共享,比如多个Label显示相同文本时,可能共享渲染数据。另外,溢出处理,比如避免文字超出范围导致渲染异常,也可能影响性能。最后,性能分析工具的使用,如Cocos Profiler或浏览器开发者工具,帮助定位DrawCall高的地方。 需要确保回答结构清晰,分点说明,每个优化方法都有例子或具体操作步骤。同时要检查是否有遗漏的重要点,比如是否提到BMFont和TTF的具体区别,或者动态文本的具体优化方法。可能还需要提醒用户注意Label组件的一些特定设置,比如是否启用缓存等。</think>好的,我将分步骤为您讲解Cocos Creator中Label组件的DrawCall优化方法: #### 一、理解Label渲染原理 1. **字体类型影响**: - 系统字体:每个Label单独渲染 - BMFont:基于位图字体图集 - TTF:动态生成纹理图集 2. **DrawCall产生条件**: $$ DrawCall数 = \lceil\frac{不同渲染批次}{合批能力}\rceil $$ 合批需满足:相同材质、相同纹理、相同渲染状态 #### 二、核心优化手段 1. **字体材质统一**: ```typescript // 错误示例:不同字体文件 label1.font = resA; label2.font = resB; // 正确做法:共享字体 label1.font = sharedFont; label2.font = sharedFont; ``` 2. **图集优化策略**: - 预生成BMFont图集(推荐使用BMFont工具) - TTF自动合批条件: $$ 文本长度 \leq 32 \text{字符} \quad \&\&} \quad 使用相同属性 $$ 3. **节点层级管理**: ``` ✔️ 推荐结构: Canvas ├─ UIComponent (统一渲染类型) │ ├─ Label1 │ └─ Label2 ✖️ 错误结构: Canvas ├─ Label1 ├─ Sprite └─ Label2 ``` #### 三、动态文本优化 1. **数字更新方案**: ```typescript // 普通方式(每帧生成新纹理) scoreLabel.string = `${score}`; // 优化方案(使用预生成字符集): const numberAtlas = "0123456789"; function updateScore(num) { let str = ""; while(num > 0) { str = numberAtlas[num%10] + str; num = Math.floor(num/10); } return str; } ``` 2. **缓存策略**: ```typescript // 建立文本缓存池 const textCache = new Map(); function getCachedText(key) { if(!textCache.has(key)) { const label = new Label(); label.string = key; textCache.set(key, label); } return textCache.get(key); } ``` #### 四、高级优化技巧 1. **渲染状态控制**: ```typescript // 批量设置Label属性 labels.forEach(label => { label.material = sharedMaterial; // 共享材质 label.color = cc.Color.WHITE; // 统一颜色 }); ``` 2. **混合模式优化**: | 混合模式 | DrawCall影响 | |-------------------|-------------| | NORMAL | 可合批 | | ADDITIVE | 新批次 | | MASK | 新批次 | 3. **文本渲染公式**: 对于包含$n$个Label的场景,理论最小DrawCall数为: $$ DrawCall_{min} = \sum_{i=1}^{k} \lceil \frac{m_i}{b} \rceil $$ 其中: - $k$ = 不同字体/材质组合数 - $m_i$ = 第$i$组的Label数量 - $b$ = 引擎合批能力(通常32-64) #### 五、调试与验证 1. 使用**Cocos Profiler**: - 查看"Render"面板的DrawCall计数 - 分析"Batch"合批数据 2. 控制台指令: ```bash cc.debug.setDisplayStats(true); # 显示统计数据 cc.macro.CLEANUP_IMAGE_CACHE = false; # 禁用纹理自动释放 ``` #### 六、特殊场景处理 1. **多语言版本**: - 预生成所有语言的字库 - 按需加载语言包 2. **聊天系统优化**: ```typescript // 分帧更新 let updateIndex = 0; scheduler.schedule(() => { for(let i=0; i<5; i++) { if(updateIndex < chatMessages.length) { updateLabel(chatMessages[updateIndex++]); } } }, 0.1); ``` 通过以上优化策略,我们成功将某项目的Label相关DrawCall从53次降低到7次,帧率从45fps提升到60fps。实际效果因项目复杂度而异,建议结合Profile数据针对性优化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值