优化图形性能
阴影投射物??
绘制调用批处理
要在屏幕上绘制游戏对象,引擎必须向图形 API(例如 OpenGL 或
Direct3D)发出绘制调用。绘制调用通常为资源密集型操作,图形
API 为每次绘制调用执行大量工作,从而导致 CPU
端的性能开销。此开销的主要原因是绘制调用之间的状态变化(例如切换到不同材质),而这种情况会导致图形驱动程序中执行资源密集型验证和转换步骤。
- 动态批处理:对于足够小的网格,此方法会在 CPU
上转换网格的顶点,将许多相似顶点
组合在一起,并一次性绘制它们。 - 静态批处理:将静态(不移动游戏对象
组合成大网格,并以较快的速度渲染它们。
与手动合并游戏对象相比,内置批处理有几个好处;最值得注意的是,仍然可以单独剔除游戏对象。但是,也有一些缺点;静态批处理会导致内存和存储开销,动态批处理会产生一些 CPU 开销。
批处理的材质设置
只有共享相同材质的游戏对象才可一起接受批处理。因此,如果想要实现良好批处理,应在尽可能多的不同游戏对象之间共享材质。
如果两种相同材质仅在纹理上不同,
可将这些纹理组合成单个大纹理。此过程通常称为纹理镶嵌(纹理图集Texture atlases)。一旦纹理位于相同图集中,即可使用单个材质。如果需要从脚本访问共享材质属性,必须注意,修改 Renderer.material
将创建该材质的副本。应改用 Renderer.sharedMaterial 来保留共享的材质。阴影投射物即使材质不同,通常也可以在渲染时接受批处理。Unity 中的阴影投射物即使具有不同材质也可以使用动态批处理,只要阴影 pass 所需材质中的值相同即可。例如,许多板条箱可能使用具有不同纹理的材质,但是由于渲染纹理的阴影投射物不相关,所以在此情况下,它们可以一起接受批处理。
动态批处理(网格)
如果移动的游戏对象共享相同材质并满足其他条件,则 Unity 可自动在同一绘制调用中批处理这些游戏对象。动态批处理是自动完成的,无需您进行任何额外工作。
- 批处理动态游戏对象在每个顶点都有一定开销,因此批处理仅会应用于总共包含不超过
900 个顶点属性且不超过 300 个顶点的网格。
- 如果着色器使用顶点位置、法线和单个 UV,最多可以批处理 300 个顶点,而如果着色器使用顶点位置、法线、UV0、UV1 和切线,则只能批处理 180 个顶点。
- 注意:将来可能会更改属性数量限制。
- 如果游戏对象在变换中包含镜像,则不会对这些对象进行批处理(例如,具有 +1 缩放的游戏对象 A 和具有 –1 缩放的游戏对象 B 无法一起接受批处理)。
- 即使游戏对象基本相同,使用不同的材质实例也会导致游戏对象不能一起接受批处理。例外情况是阴影投射物渲染。
- 带有光照贴图的游戏对象具有其他渲染器参数:光照贴图索引和光照贴图偏移/缩放。通常,动态光照贴图的游戏对象应指向要批处理的完全相同的光照贴图位置。
- 多 pass 着色器会中断批处理。
- 几乎所有的 Unity 着色器都支持前向渲染中的多个光照,有效地为它们执行额外 pass。“其他每像素光照”的绘制调用不进行批处理。
- 旧版延迟(光照 pre-pass)渲染路径会禁用动态批处理,因为它必须绘制两次游戏对象。
因为动态批处理的工作原理是将所有游戏对象顶点转换到 CPU 上的世界空间,所以仅在该工作小于进行绘制调用的情况下,才有优势。绘制调用的资源需求取决于许多因素,主要是使用的图形 API。例如,对于游戏主机或诸如 Apple Metal 之类的现代 API,绘制调用的开销通常低得多,通常动态批处理根本没有优势。
动态批处理(粒子系统、线渲染器、轨迹渲染器)
动态批处理在用于具有 Unity 动态生成的几何体的组件时,其工作方式与用于网格时不同。
- 对于每个兼容的渲染器类型,Unity 将所有可批处理的内容构建为 1 个大型顶点缓冲区。
- 渲染器设置材质状态以用于批处理。
- Unity 将顶点缓冲区绑定到图形设备。
- 对于批处理中的每个渲染器,Unity 将偏移更新到顶点缓冲区中,然后提交新的绘制调用。
在衡量图形设备调用的成本时,渲染组件时的最慢部分是材质状态的设置。相比之下,将不同偏移处的绘制调用提交到共享顶点缓冲区中的速度非常快。 这种方法与 Unity 在使用静态批处理时提交绘制调用的方式非常相似。
静态批处理
使用静态批处理,引擎可减少任何大小的几何体的绘制调用,但前提是它共享相同材质并且不移动。这种处理方式通常比动态批处理更高效(它不会在 CPU 上转换顶点),但是使用更多内存。 为了利用静态批处理,您需要显式指定某些游戏对象是静态对象且不会在游戏中移动、旋转或缩放。为此,请使用 Inspector 中的 Static 复选框,将游戏对象标记为静态:
使用静态批处理需要额外的内存来存储组合的几何体。如果多个游戏对象在静态批处理之前共享相同几何体,则会在 Editor 中或运行时为每个游戏对象创建几何体的副本。这可能并非总是好办法;有时您必须避免为某些游戏对象进行静态批处理,这样会牺牲渲染性能,但可保持较小的内存占用量。例如,在茂密森林关卡中,将树标记为静态可能会产生严重的内存影响。 在内部,静态批处理的工作原理是将静态游戏对象转换到世界空间并为它们构建一个共享的顶点和索引缓冲区。如果已启用 Optimized Mesh Data__(在 Player__ 设置中),则 Unity 会在构建顶点缓冲区时删除任何着色器变体未使用的任何顶点元素。为了执行此操作,系统会进行一些特殊的关键字检查;例如,如果 Unity 未检测到 LIGHTMAP_ON 关键字,则会从批处理中删除光照贴图 UV。然后,针对同一批次中的可见游戏对象,Unity 会执行一系列简单的绘制调用,每次调用之间几乎没有状态变化。在技术上,Unity 不会减少 API 绘制调用,而是减少它们之间的状态变化(这正是消耗大量资源的部分)。在大多数平台上,批处理限制为 64k 个顶点和 64k 个索引(OpenGLES 上为 48k 个索引,在 macOS 上为 32k 个索引)。
当前,仅对网格渲染器、轨迹渲染器、线渲染器、粒子系统和精灵渲染器进行批处理。这意味着不会对蒙皮网格、布料和其他类型的渲染组件进行批处理。
渲染器仅与其他相同类型的渲染器一起接受批处理。
半透明着色器通常要求游戏对象按照从后到前的顺序进行渲染,从而实现透明性。Unity
首先按此顺序对游戏对象排序,然后尝试对它们进行批处理,但是因为必须严格满足顺序,所以这通常意味着可以实现比不透明游戏对象更少的批处理。
手动组合彼此接近的游戏对象可以是绘制调用批处理的极好替代方法。例如,一个带有大量抽屉的静态橱柜通常只需在
3D 建模应用程序中或者使用 Mesh.CombineMeshes
来组合成一个网格。
角色建模的优化
Statistics 窗口
| 选项 | 含义 |
|---|---|
| Time per frame and FPS | 处理和渲染一个游戏帧所花费的时间(及其倒数,即每秒帧数)。请注意,此数字仅包括进行帧更新和渲染 Game 视图所用的时间;不包括在 Editor 中绘制 Scene 视图、检视面板处理和其他仅限于 |
| Batches | “批处理 (Batching)”可让引擎尝试将多个对象的渲染组合到一个内存块中以便减少由于资源切换而导致的 CPU 开销。 |
| Saved by batching | 合并的批次数。为确保良好的批处理,应尽可能在不同对象之间共享材质。更改渲染状态会将批次分成具有相同状态的组。 |
| Tris 和 Verts | 绘制的三角形和顶点的数量。在针对低端硬件进行优化时,这一点非常重要 |
| Screen | 屏幕大小以及抗锯齿级别和内存使用情况。 |
| SetPass | 渲染 pass 的数量。每个 pass 都需要 Unity 运行时绑定一个新的着色器,这可能会带来 CPU 开销。 |
| Visible Skinned Meshes | 渲染的蒙皮网格的数量。 |
| Animations | 播放的动画的数量。 |
优化着色器加载时间
着色器是在 GPU 上执行的小程序,加载它们可能需要一些时间。每个单独的 GPU 程序通常不会花费很多时间来加载,但着色器通常在内部有很多“变体”。
例如,标准着色器在完全编译后,最终将成为数千个略微不同的 GPU 程序。这种情况可产生两个潜在问题:
- 大量的这些着色器变体将增加游戏构建时间和游戏数据大小。
- 在游戏过程中加载大量着色器变体将很慢并占用内存。
着色器构建时剥离
在构建游戏时,Unity 可能检测到游戏不使用某些内部着色器变体,并从构建数据中跳过它们。构建时剥离将用于以下各项:
- 各个着色器功能(针对使用 #pragma shader_feature 的着色器)。如果所使用的材质都不使用特定变体,则该变体不会包含在构建中。请参阅内部着色器变体文档。在内置着色器中,标准着色器会使用构建时剥离。
- 任何场景未使用的可处理雾效和光照贴图模式的着色器变体不会包含在游戏数据中。如果要覆盖此行为,请参阅 Graphics 窗口。
上述的组合通常会大大减小着色器数据大小。例如,完全编译后的标准着色器将占用几百兆字节,但在典型的项目中,通常最终仅占用几兆字节(并且通常会由应用程序打包过程进一步压缩)。
默认的 Unity 着色器加载行为
在所有默认设置下,Unity 将 shaderlab 着色器对象加载到内存中,但在实际需要之前不会创建内部着色器变体。
这意味着仍然可以使用包含在游戏构建中的着色器变体,但是在需要它们之前不会有内存或加载时间成本。例如,着色器总是包含一个变体用于处理带阴影的点光源,但是如果一直未能在游戏中使用带阴影的点光源,那么加载这个特定的变体是没有意义的。
然而,这种默认行为有一个缺点,即第一次需要某个着色器变体时可能会出现暂时性中断问题,这是因为必须将新的 GPU 程序代码加载到图形驱动程序中。在游戏过程中通常不希望发生这种问题,因此 Unity 提供了 ShaderVariantCollection 资源来帮助解决该问题。