Unity引擎源码札记(一):Dynamic Batching

发表于2017-02-24
评论1 1.14w浏览

1.   概述

本文档研究Unity的动态合批(Dynamic Batching)特性,修改引擎源码(基于Unity 4.6)进行性能分析,讨论实现引擎底层定制化功能可行性。文档背景在于进一步理解Dynamic Batching,探索引擎底层做定制优化的可能性,在此做个记录。

2.   Dynamic Batching

Dynamic Batching条件

简单地说,Dynamic Batching是自动进行的,只要对象使用相同的材质并符合如下规则,就会组成Batch。官方文档给出的条件如下:

http://docs.unity3d.com/Manual/DrawCallBatching.html):

1)顶点个数:由于物体顶点太多开销会过大,因此仅适合小于900个顶点的Meshes,如果你的Shader有使用Vertex PositionNormal或是Single UV,顶点数下降到300;如果还有UV0 UV1 Tangent,那只能处理180顶点(这是参考值,会随着版本演化而改变);

2)对于缩放来说等比缩放和非等比缩放的物体会分开处理;

3)使用不同材质的实例化游戏对象,会导致批处理失败;

4)拥有lightmap的对象,含有额外的渲染属性,例如lightmap的索引、偏移以及缩放等系数;

5)如果物体ShaderMulti-pass会中止批量处理;

6)物体接收实时阴影不会纳入批处理。

部份动态合拼条件源码:

阈值:

Vertex Indices threshold

Dynamic batching过程中,会计算下一个mesh需要的顶点索引数目,如果vertex index 超过32000(引擎源码中kDynamicBatchingIndicesThreshold)时,将停止当前批次。比如,假定物件A符合dynamic batching的条件,共有有45个实例,第45个实例合批时,vertex index 已超过32000,将生成另外一个批次绘制其余实例。参考下图对比,场景中增加第45wp_l_32实例时,Draw call数目增加1saved by batching没有变化。(Statistics窗口显示的统计数据,其中一个draw call为相机clear, 下同。)

 

 

 

CPU开销

动态合批从源码上看,是会带来一定的CPU开销的,下面通过测试分析增加顶点数阈值的可行性。

默认引擎动态合批的顶点数阈值(kDynamicBatchingVerticesThreshold)为300,修改为500,对比修改前后同一场景的性能消耗。

两个模型:A( wp_l_032)  B( wp_l_033) 顶点数分别为210411。并且放置同一场景中,有200A实例,50B实例。

 

 

引擎默认kDynamicBatchingVerticesThreshold =300时,draw call56,除开相机clear需要1draw call200A合并为5draw call进行绘制,50B需要50draw call(顶点数>300,不符合动态合批)。

修改kDynamicBatchingVerticesThreshold =500时,draw call8,除开相机clear需要1 draw call200A合并为5draw call进行绘制,50个合并为2draw call(顶点数<500,符合动态合批)。

 

可以看出修改后draw call 大大减少了,减轻了GPU负载。以下使用WeTest对该场景进行真机测试(引擎为release版本)。

测试环境:

测试数据:

可以看出,threshold=300threshold=500两种情况下,内存占用相似,但后者的CPU占用率比前者高出不少,FPS反而因此略有下降。因而,Dynamic batching并非尽可能多用,视具体情况分析。如果GPU负载品瓶颈,CPU负载又相对宽裕,可以尽可能符合dynamic batching条件或定制修改threshold,扩大合并批次的mesh范围;如果GPU负载尚可,CPU负载过高,则不建议修改threshold

 

项目(前期)场景资源统计数据

对项目中,常见场景的mesh资源及默认thresholddraw call情况做采样统计,数据如下:

顶点数

Scene1

Scene2

Scene3

Scene4

Scene5

Scene6

Scene7

Scene8

0-300

263

154

99

145

126

129

138

163

300-400

39

6

8

6

11

14

14

1

400-500

31

9

11

7

23

17

11

7

500-600

6

8

5

4

10

16

12

1

600-700

5

4

10

2

5

4

6

2

700-800

7

8

2

2

7

11

5

12

800-900

1

6

7

4

0

4

3

20

900-1000

2

5

5

6

9

13

6

4

1000+

44

56

50

37

38

56

42

81

Draw call

296

125

111

176

148

152

136

130

Save by batching

186

11

12

8

18

0

13

4

 

数据

l  大多数场景下300顶点以下,1000顶点以上的对象居多;300顶点以下的对象占50%以上,500顶点以下的对象占60%以上,最多可以占到83%(多人战斗);

l  动态合批节省的draw call ( Save by Batching ) 多数场景下并不多,是因受限于材质、非等比缩放等因素;多人战斗情况下,特效、装备等小物件较多,因此动态合批的作用较大;

 

结合以上情况,默认的threshold=300能覆盖50%的对象,若将threshold改为500能覆盖60%以上的场景对象,但实际意义不大,节省少量的draw call的同时,会增加CPU负载。

3.   NonUniformScale

如果mesh使用非等比缩放,将会生成一份Scaled Mesh

非等比缩放的动态对象(如每帧更新缩放值曲线的特效对象)会导致的VBO update明显增多,在Adreno Profiler里显示heavy call(下图红色部分)。如下图,某技能特效,使用非等比缩放时,VBO Data Updates 17,优化后不使用非等比缩放为3heavy call也大在减少,有利减轻GPU负载。

4.   小结

综上,在资源上应尽量符合Unity官方文档给出的Dynamic Batching的条件,特别是避免非等比缩放,不仅无法动态合批,对于每帧更新的非等比缩放对象,会造成大量VBO Updates,影响性能。另外,动态合批也会增加CPU性能消耗,从引擎底层加大阈值并非好的解决方案,应结合项目当前实际性能状况。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引