DrawCall的优化以及一些相关测试

来源:互联网 发布:淘宝千里眼下载 编辑:程序博客网 时间:2024/05/24 03:42

之前在蛮牛写了一篇帖子作介绍http://www.manew.com/thread-45299-1-1.html


Drawcall的原理:

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:Yubo Zhang
链接:http://www.zhihu.com/question/29730328/answer/45414167
来源:知乎

Draw call一般包含了要画什么(点/线/三角形),顶点数据在哪里(显存地址),是怎么组织的。图形API把draw call丢给驱动,驱动丢给GPU前端,GPU开始解释并执行。单个Drawcall的代价本身并不大,但提交的任务可大可小,如果用大量的draw call每次提交少量任务,那么问题就来了,原因在于GPU处理任务的速度可能会大于CPU提交任务的速度,导致pipeline经常空闲下来。要优化有两种办法,一是合并任务,减少提交次数,二是改革图形API和驱动,进一步减少单个Draw call的代价,比如避免频繁的context switch而是采用多个任务提交队列,并发挥多核CPU优势,优化提交任务速度。


------------------------------------------------------------------------------------------------------------

下面是自己做的一些测试和推算,以及在网上看收集的一些资料


Drawcall的限制和cpu之间的关系:

Fps是指游戏运行时的帧率,表示每秒要绘制多少帧,一般要让游戏保持肉眼可见的流畅度需要保持在30帧以上,而每帧可能会有多次渲染,(cpu告诉GPU场景中需要渲染哪些物件,物件的材质,顶点,图片等信息,即一次Drawcall),Batches代表每帧有多少次Drawcall(即渲染多少次)。

NVIDIA GDC曾提出,25K batchs/sec会吃满 1GHz CPU100的使用率。所以他们推出了一条公式,来预估游戏中大概可以 Run多少个 Batch


举个例子:如果你的目标是游戏跑30FPS、使用2GHzCPU20%的工作量拨给Draw Call来使用,那你每帧可以有多少Draw Call呢?

 333 Batchs/Frame = 25K * 2 * (0.2/30)

假设在800MhziTouch5中,如果目标游戏跑30fpscpu 20%的工作量拨给drawcall,那么可以有多少drawcall呢? 

25k*0.8*(0.2/30)=133.3.

在iTouch5上实际测试数据:

Drawcall                        CPU

4                               23%

5                               23%

6                               23%--24%

7                                                      24%

8                                                      24%

9                                                      24%--25%

10                                                     24%--25%

11                                                     25%

12                                                     25%

52                                                     34%

68                                                     37%

100                                                   44%

370                                                   100%

从上面数据可以得到:在30fps的游戏中没1%CPU使用率大概能承载4.6左右的drawcall370-4/100-23=4.7.就是说20%cpu大概能够承载20*4.6=95DrawCall。理论数据2Ghz0.8GHz相差2.56倍,333/2.56=130drawCall,实际测试数据大概值为95(因为CPU占用率的值并不是稳定的,所以取相对比较稳定的值作为概值)。另外在Unity发布的App中可能会有其他因素影响到CPU的工作状态,所以认为该公式的计算结果可以作为性能边界的估值,并不是精确值。

IPhone6CPU频率为1.4Ghz,所以游戏跑30fps20%CPU使用,每帧可以跑的DrawCall数为:25k*1.4*(0.2/30)=233。(概值)

 

那既然 Batch是个箱子,里头装着物件的顶点资料,再依据我们上面的描述,那表示同

材质或 Shader的物件,可以合并成一个 Batch送往 GPU,这样就是最省事的方法!

Unity Player Setting里的两个功能选项 Static Batching Dynamic Batching。功能描述如下:

  1. Static Batching 是将标明为 Static 的静态物件,如果在使用相同材质球的条件下,Unity 会自动帮你把这两个物件合并成一个 Batch,送往 GPU 来处理。这功能对效能上非常的有帮助,所以是需要付费才有的。
  2. Dynamic Batching 是在物件小于300面的条件下(不论物件是否为静态或动态),在使用相同材质球下,Unity就会自动帮你合合并成一个 Batch 送往 GPU 来处理。

Unity对Drawcall的一些优化:

在屏幕上渲染物体,引擎需要发出一个绘制调用来访问图形APIiOS系统中为OpenGL ES)。每个绘制调用需要进行大量的工作来访问图形API,从而导致了CPU方面显著的性能开销。

Unity在运行时可以将一些物体进行合并,从而用一个绘制调用来渲染他们。这一操作,我们称之为批处理。一般来说,Unity批处理的物体越多,你就会得到越好的渲染性能。

Unity中内建的批处理机制所达到的效果要明显强于使用几何建模工具(或使用Standard Assets包中的CombineChildren脚本)的批处理效果。这是因为,Unity引擎的批处理操作是在物体的可视裁剪操作之后进行的。Unity先对每个物体进行裁剪,然后再进行批处理,这样可以使渲染的几何总量在批处理前后保持不变。但是,使用几何建模工具来拼合物体,会妨碍引擎对其进行有效的裁剪操作,从而导致引擎需要渲染更多的几何面片。

材质

只有拥有相同材质的物体才可以进行批处理。因此,如果你想要得到良好的批处理效果,你需要在程序中尽可能地复用材质和物体。

如果你的两个材质仅仅是纹理不同,那么你可以通过纹理拼合操作来将这两张纹理拼合成一张大的纹理。一旦纹理拼合在一起,你就可以使用这个单一材质来替代之前的两个材质了。

如果你需要通过脚本来访问复用材质属性,那么值得注意的是改变Renderer.material将会造成一份材质的拷贝。因此,你应该使用Renderer.sharedMaterial来保证材质的共享状态。

动态批处理

如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。

动态批处理操作是自动完成的,并不需要你进行额外的操作。

Tips:

提醒:

1批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。

2如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0UV1和切向量,那你只能批处理180顶点以下的物体。

 

3、请注意:属性数量的限制可能会在将来进行改变。

4不要使用缩放尺度(scale)。分别拥有缩放尺度(1,1,1)(2,2,2)的两个物体将不会进行批处理。

5统一缩放尺度的物体不会与非统一缩放尺度的物体进行批处理。

使用缩放尺度(1,1,1) (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1)(1,3,1)的两个物体将可以进行批处理。

6使用不同材质的实例化物体(instance)将会导致批处理失败。

7、拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。

8多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道

9、预设体的实例会自动地使用相同的网格模型和材质。

静态批处理

 

相对而言,静态批处理操作允许引擎对任意大小的几何物体进行批处理操作来降低绘制调用(只要这些物体不移动,并且拥有相同的材质)。因此,静态批处理比动态批处理更加有效,你应该尽量低使用它,因为它需要更少的CPU开销。


为了更好地使用静态批处理,你需要明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放。想完成这一步,你只需要在检测器Inspector)中将Static复选框打勾即可,如下图所示:





 

使用静态批处理操作需要额外的内存开销来储存合并后的几何数据。在静态批处理之前,如果一些物体共用了同样的几何数据,那么引擎会在编辑以及运行状态对每个物体创建一个几何数据的备份。这并不总是一个好的想法,因为有时候,你将不得不牺牲一点渲染性能来防止一些物体的静态批处理,从而保持较少的内存开销。比如,将浓密森里中树设为Static,会导致严重的内存开销。

如何对动态加载的静态物体进行静态合批操作:

GameObject go = (GameObject)Instantiate(gameObjectObj,Vector3.right*4+ Vector3.left*2* l,Quaternion.LookRotation(Vector3.up,Vector3.forward));

go.isStatic = true;//将实例化的物体设置为静态

go.transform.parent= root.transform;

gosList.Add(go);

gos = gosList.ToArray();

StaticBatchingUtility.Combine(gos, root);静态合并


参考链接:http://blog.csdn.net/molti/article/details/42495993

0 0
原创粉丝点击