跳转到主要内容

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

demi 提交于

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

<hr>

<font size="4" style="line-height: 45px;" color="#c200ff"><strong>什么是Draw Call?</strong></font>

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

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

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

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

<font style="line-height: 40px;"><strong>其核心流程包含三个阶段:</strong></font>

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

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

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

<font style="line-height: 40px;"><strong>关键特性:</strong></font>

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

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

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

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

<center><img width="600" src="https://cdn.eetrend.com/files/2025-10/%E5%8D%9A%E5%AE%A2/100595960-4050…; alt="" /></center><br>

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

<hr>

<font size="4" style="line-height: 45px;" color="#c200ff"><strong>为什么Draw Call 多了会影响帧率?</strong></font>

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

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

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

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

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

<center><img src="https://cdn.eetrend.com/files/2025-10/%E5%8D%9A%E5%AE%A2/100595960-4050…; alt="" /></center><br>

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

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

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

<center><img width="600" src="https://cdn.eetrend.com/files/2025-10/%E5%8D%9A%E5%AE%A2/100595960-4050…; alt="" /></center><br>

<hr>

<font size="4" style="line-height: 45px;" color="#c200ff"><strong>如何减少Draw Call?</strong></font>

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

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

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

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

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

<center><img width="600" src="https://cdn.eetrend.com/files/2025-10/%E5%8D%9A%E5%AE%A2/100595960-4050…; alt="" /></center><br>

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

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

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

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

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

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

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

<hr>

<font color="#9a9a9a">本文转自:<a href="https://mp.weixin.qq.com/s/GsQQGgjHbSNMpCFuNKuzYQ"><font color="#9a9a9a">字符无限科技</font></a>,转载此文目的在于传递更多信息,版权归原作者所有。如不支持转载,请联系小编demi@eetrend.com删除。</font>
<br>