游戏卡顿元凶竟然是 Draw Call!

游戏时遇到画面掉帧、操作延迟,大概率和一个叫Draw Call的指标有关。它是游戏渲染的核心环节,也是性能优化绕不开的坎,哪怕是Unity、UE 引擎的资深开发者,也得在它身上下功夫。


什么是Draw Call?

Draw Call仅仅是一条指令!Draw Call指令从CPU传到GPU,渲染一个网格。指令只指向一个被渲染的网格并且不包含任何材质信息。

在发出指令后,GPU的渲染状态值(材质、纹理、shader等)和所有的顶点数据通过神奇的代码转化为以一个信息,然后再在你的屏幕上呈现出美丽的画面。

渲染就是在做一个巨大数量的小任务,比如计算成千上万的顶点和在屏幕上绘制以百万计的像素。

Draw Call 本身的含义很简单,就是CPU调用图像编程接口,如OpenGL 中的glDrawElements 命令或者DirectX 中的DrawlndexedPrimitive命令,以命令GPU 进行渲染的操作。

其核心流程包含三个阶段:

数据准备:CPU将网格数据、纹理、材质属性等资源从内存(RAM)传输至GPU显存(VRAM);

状态配置:设置渲染管线状态(如着色器、混合模式、深度测试)和全局参数(如光照、投影矩阵);

指令提交:调用glDrawElements或DrawIndexedPrimitive等API触发GPU渲染。

关键特性:

命令缓冲区机制:CPU与GPU通过Command Buffer实现异步通信,CPU写入指令,GPU按队列顺序执行;

渲染状态切换成本:每次材质、纹理或着色器变更需重新配置全局状态,产生额外开销。

你在游戏里看到的每棵树、每个角色、每道特效,背后都需要 CPU 发一次(或多次)命令,告诉 GPU “该画这个东西了”。比如屏幕上有 100 棵树,默认情况下可能就有 100 个 Draw Call,GPU 收到命令后才会执行渲染操作。

这里要明确一个关键点:Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。这个命令仅仅会指向一个需要被渲染的图元(primitives)列表,而不会再包含任何材质信息,这是因为我们已经在上一个阶段中完成了!


一个常见的误区是, Draw Call 中造成性能问题的元凶是GPU,认为GPU 上的状态切换是耗时的,其实不是的,真正“拖后腿”其实的是CPU。


为什么Draw Call 多了会影响帧率?

我们先来做一个实验:先创建10000 个小文件,每个文件的大小为1KB,然后把它们从一个文件夹复制到另一个文件夹。你会发现,尽管这些文件的空间总和不超过10MB ,但要花费很长时间。

现在,我们再来创建一个单独的文件,它的大小是10MB,然后也把它从一个文件夹复制到另一个文件夹。而这次复制的时间却少很多!

这是为什么呢?明明它们所包含的内容大小是一样的。原因在于,每一个复制动作需要很多额外的操作,例如分配内存、创建各种元数据等。

如你所见,这些操作将造成很多额外的性能开销,如果我们复制了很多小文件,那么这个开销将会很大。

渲染的过程虽然和上面的实验有很大不同,但从感性角度上是很类似的。在每次调用Draw Call 之前, CPU 需要向GPU 发送很多内容,包括数据、状态和命令等。


在这一阶段, CPU 需要完成很多工作,例如检查渲染状态等。而一旦CPU 完成了这些准备工作, GPU 就可以开始本次的渲染。

GPU 的渲染能力是很强的,渲染200 个还是2000 个三角网格通常没有什么区别,因此渲染速度往往快于CPU 提交命令的速度。

如果Draw Call 的数量太多, CPU 就会把大量时间花费在提交Draw Call 上,造成CPU 的过载。



如何减少Draw Call?

尽管减少Draw Call 的方法有很多,但我们这里仅讨论使用批处理(Batching )的方法。

我们讲过,提交大量很小的Draw Call 会造成CPU 的性能瓶颈,即CPU 把时间都花费在准备Draw Call 的工作上了。

那么,一个很显然的优化想法就是把很多小的DrawCall 合并成一个大的Draw Call ,这就是批处理的思想。

需要注意的是,由于我们需要在CPU 的内存中合并网格,而合并的过程是需要消耗时间的。因此,批处理技术更加适合于那些静态的物体,例如不会移动的大地、石头等,对于这些静态物体我们只需要合并一次即可。

当然,我们也可以对动态物体进行批处理。但是,由于这些物体是不断运动的,因此每一帧都需要重新进行合并然后再发送给GPU,这对空间和时间都会造成一定的影响。


在游戏开发过程中,为了减少Draw Call 的开销,需要注意:

避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时,考虑是否可以合并它们。

避免使用过多的材质。尽量在不同的网格之间共用同一个材质。

合并的网格会在一次渲染任务中进行绘制,他们的渲染数据,渲染状态和shader都是一样的,因此合并的条件至少是:同材质、同贴图、同shader。最好网格顶点格式也一致。

合并本身有消耗,因此尽量在编辑器下进行合并。

确实需要在运行时合并的,将静态的物体和动态的物体分开合并:静态的合并一次就可以,动态的只要有物体发生变换就要重新合并。

Draw Call 作为游戏性能的关键指标,优化的核心从来不是让GPU 少画,而是让 CPU 少发命令。掌握批处理技巧,再注意开发中的细节,就能有效减少卡顿,让游戏画面更流畅。


本文转自:字符无限科技,转载此文目的在于传递更多信息,版权归原作者所有。如不支持转载,请联系小编demi@eetrend.com删除。

最新文章