GPU optimization

简介

对新的图形功能和进步的需求几乎可以保证你必会遇到图形瓶颈。有些瓶颈可能出现在CPU端,例如在Godot引擎内部的计算中,为渲染准备对象。瓶颈也可能发生在CPU上的图形驱动中,它将指令分类传递给GPU,以及这些指令的传输过程。最后,瓶颈也会发生在GPU本身。

渲染中的瓶颈发生在哪里,高度依赖于硬件。特别是移动GPU可能会在桌面上轻松运行的场景中挣扎。

了解和调查GPU瓶颈与CPU上的情况略有不同。这是因为,通常情况下,你只能通过改变你给GPU的指令来间接改变性能。另外,测量起来可能更困难。在许多情况下,衡量性能的唯一方法是通过检查每帧渲染时间的变化。

绘制调用,状态更变和api

注解

以下部分与最终用户无关,但对于提供与后面章节相关的背景信息是有用的。

Godot通过图形API(OpenGL、OpenGL ES或Vulkan)向GPU发送指令。所涉及的通信和驱动活动可能非常昂贵,尤其是在OpenGL和OpenGL ES中。如果我们能以驱动和GPU喜欢的方式提供这些指令,就能大大提高性能。

OpenGL中几乎每一个API命令都需要一定的验证,以确保GPU处于正确的状态。即使是看似简单的命令,也会导致一连串的幕后工作。因此,我们的目标是将这些指令减少到最低限度,并尽可能地将相似的对象分组,以便它们可以一起渲染,或者以最少的数量进行这些昂贵的状态变化。

2D batching

在2D中,单独处理每个项目的成本可能会非常高--屏幕上很容易有成千上万的项目。这就是为什么使用2D 批处理 的原因。多个类似的项目被归为一组,并通过一个单一的绘制调用进行批量渲染,而不是对每个项目进行单独的绘制调用。此外,这意味着状态变化、材质和纹理变化可以保持在最低限度。

For more information on 2D batching, see Optimization using batching.

3D batching

在3D中,我们的目标仍然是尽量减少绘制调用和状态变化。然而,将多个对象批量合并到一个绘图调用中可能比较困难。3D网格往往由数百个或数千个三角形组成,而实时组合大型网格的成本非常高。随着每个网格的三角形数量的增加,加入它们的成本很快就超过了带来的好处。一个更好的选择是 提前加入网格 (静态网格之间的关系)。这可以由设计师完成,或者在Godot中以编程方式完成。

在3D中把物体批处理在一起也是有成本的。几个对象渲染成一个,就不能单独剔除。如果将屏幕外的整座城市与屏幕上的一片草地连接在一起,那么它仍然会被渲染。因此,当试图将3D对象批量连接在一起时,应该始终考虑到对象的位置和剔除。尽管如此,加入静态对象的好处往往大于其他考虑因素,特别是对于大量的远距离或低多边形物体。

有关特定于3D的优化的更多信息,请参阅 Optimizing 3D performance

Reuse Shaders and Materials

The Godot renderer is a little different to what is out there. It's designed to minimize GPU state changes as much as possible. SpatialMaterial does a good job at reusing materials that need similar shaders. if custom shaders are used, make sure to reuse them as much as possible. Godot's priorities are:

  • Reusing Materials: The fewer different materials in the scene, the faster the rendering will be. If a scene has a huge amount of objects (in the hundreds or thousands), try reusing the materials. In the worst case, use atlases to decrease the amount of texture changes.

  • Reusing Shaders: If materials can't be reused, at least try to re-use shaders (or SpatialMaterials with different parameters but the same configuration).

If a scene has, for example, 20,000 objects with 20,000 different materials each, rendering will be slow. If the same scene has 20,000 objects, but only uses 100 materials, rendering will be much faster.

Pixel cost versus vertex cost

You may have heard that the lower the number of polygons in a model, the faster it will be rendered. This is really relative and depends on many factors.

On a modern PC and console, vertex cost is low. GPUs originally only rendered triangles. This meant that every frame:

  1. All vertices had to be transformed by the CPU (including clipping).

  2. All vertices had to be sent to the GPU memory from the main RAM.

Nowadays, all this is handled inside the GPU, greatly increasing performance. 3D artists usually have the wrong feeling about polycount performance because 3D DCCs (such as Blender, Max, etc.) need to keep geometry in CPU memory for it to be edited, reducing actual performance. Game engines rely on the GPU more, so they can render many triangles much more efficiently.

On mobile devices, the story is different. PC and console GPUs are brute-force monsters that can pull as much electricity as they need from the power grid. Mobile GPUs are limited to a tiny battery, so they need to be a lot more power efficient.

To be more efficient, mobile GPUs attempt to avoid overdraw. Overdraw occurs when the same pixel on the screen is being rendered more than once. Imagine a town with several buildings. GPUs don't know what is visible and what is hidden until they draw it. For example, a house might be drawn and then another house in front of it (which means rendering happened twice for the same pixel). PC GPUs normally don't care much about this and just throw more pixel processors to the hardware to increase performance (which also increases power consumption).

Using more power is not an option on mobile so mobile devices use a technique called tile-based rendering which divides the screen into a grid. Each cell keeps the list of triangles drawn to it and sorts them by depth to minimize overdraw. This technique improves performance and reduces power consumption, but takes a toll on vertex performance. As a result, fewer vertices and triangles can be processed for drawing.

Additionally, tile-based rendering struggles when there are small objects with a lot of geometry within a small portion of the screen. This forces mobile GPUs to put a lot of strain on a single screen tile, which considerably decreases performance as all the other cells must wait for it to complete before displaying the frame.

To summarize, don't worry about vertex count on mobile, but avoid concentration of vertices in small parts of the screen. If a character, NPC, vehicle, etc. is far away (which means it looks tiny), use a smaller level of detail (LOD) model. Even on desktop GPUs, it's preferable to avoid having triangles smaller than the size of a pixel on screen.

使用时要注意额外的顶点处理:

  • 蒙皮(骨骼动画)

  • 变形(形态键)

  • Vertex-lit objects (common on mobile)

像素/片段着色器和填充速率

与顶点处理相比,片段着色器(每像素)的成本在这些年里急剧增加。屏幕分辨率提高了(4K屏幕的面积是829400像素,而老式640×480 VGA屏幕的面积是307200,是27倍),但片段着色器的复杂度也爆炸式增长。基于物理的渲染需要对每个片段进行复杂的计算。

你可以很容易地测试一个项目是否受到填充率限制。关闭V-Sync以防止每秒帧数的上限,然后比较使用大窗口运行时的每秒帧数和使用非常小的窗口运行时的帧数。如果使用阴影,你也可以从同样减少阴影贴图大小中获益。通常,你会发现使用小窗口的FPS会增加不少,这说明你在某种程度上受到了填充率的限制。另一方面,如果FPS几乎没有增加,那么你的瓶颈就在其他地方。

你可以通过减少GPU的工作量来提高填充率限制项目的性能。你可以通过简化着色器(如果你使用的是 :ref:`SpatialMaterial <class_SpatialMaterial> `,也许可以关闭昂贵的选项),或者减少使用的纹理数量和大小来实现。

在针对移动设备时,考虑使用你能合理负担得起的最简单的着色器.

Reading textures

片段着色器的另一个因素是读取纹理的成本。读取纹理是一项昂贵的操作,尤其是在一个片段着色器中从多个纹理中读取时。另外,考虑到过滤可能会进一步减慢它的速度(mipmaps之间的三线性过滤,以及平均)。读取纹理在功耗方面也很昂贵,这在手机上是个大问题。

如果您使用第三方着色器或编写自己的着色器,请尽量使用需要尽可能少的纹理读取的算法。

纹理压缩

By default, Godot compresses textures of 3D models when imported using video RAM (VRAM) compression. Video RAM compression isn't as efficient in size as PNG or JPG when stored, but increases performance enormously when drawing large enough textures.

这是因为纹理压缩的主要目标是在内存和GPU之间减少带宽。

In 3D, the shapes of objects depend more on the geometry than the texture, so compression is generally not noticeable. In 2D, compression depends more on shapes inside the textures, so the artifacts resulting from 2D compression are more noticeable.

作为警告,大多数Android设备不支持具有透明度的纹理的纹理压缩(仅不透明),因此请记住这一点。

注解

即使在3D中,"像素艺术 "纹理也应该禁用VRAM压缩,因为这会对其外观产生负面影响,而不会因为其低分辨率而显著提高性能。

后处理和阴影

就片段着色活动而言,后期处理效果和阴影也可能很昂贵。始终测试这些对不同硬件的影响。

减少阴影图的大小可以提高性能 ,无论是在写还是读取阴影贴图方面。除此之外,提高阴影性能的最好方法是关闭尽可能多的灯光和物体的阴影。较小或较远的OmniLights/SpotLights通常可以禁用它们的阴影,而对视觉影响很小。

Transparency and blending

透明物体对渲染效率带来了特殊的问题。不透明的对象(尤其是在3D中)基本上可以以任意顺序渲染,Z-缓冲区将确保只有最前面的对象得到阴影。透明或混合对象则不同,在大多数情况下,它们不能依赖Z-缓冲区,必须以 "画家顺序"(即从后到前)渲染才能看起来正确。

透明对象的填充率也特别差,因为每一个项目都要绘制,即使之面会在上面绘制其他透明对象。

不透明的对象不需要这样做。它们通常可以利用Z-缓冲区,只先向Z-缓冲区写入数据,然后只在 "胜利" 的片段上执行片段着色器,也就是在某一像素处处于前面的对象。

在多个透明对象重叠的情况下,透明度特别昂贵。通常情况下,使用透明区域越小越好,以尽量降低这些填充率要求,尤其是在移动端。事实上,在很多情况下,渲染更复杂的不透明几何体最终可能比使用透明度来 "作弊" 更快。

Multi-platform advice

如果您的目标是在多个平台上发布,请在您的所有平台上(尤其是移动平台)上进行 早期经常 性测试。在桌面上开发游戏,但试图在最后一刻将其移植到移动设备,这是灾难的根源。

一般来说,你应该从最底的共性设计你的游戏,然后为更强大的平台添加可选的增强功能。例如,你可能希望在同时针对桌面和移动平台的情况下,同时使用GLES2后台。

移动端和图块渲染

如上所述,移动设备上的GPU与桌面上的GPU工作方式有很大不同。大多数移动设备都使用图块渲染器。图块渲染器将屏幕分割成规则大小的图块,这些图块可以放入超快的缓存中,从而减少了对主内存的读和写操作次数。

不过也有一些缺点。图块渲染会让某些技术变得更加复杂,执行起来也更加昂贵。依赖于不同图块渲染的结果,或者依赖于早期操作的结果被保存的图块可能会非常慢。要非常小心地测试着色器、视图纹理和后期处理的性能。