【Unity】优化渲染 与 overdraw

GPU的优化

GPU与CPU不同,所以侧重点自然也不一样。GPU的瓶颈主要存在在如下的方面:

  1. 填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。
  2. 像素的复杂度,比如动态阴影,光照,复杂的shader等等
  3. 几何体的复杂度(顶点数量)
  4. 当然还有GPU的显存带宽

那么针对以上4点,其实仔细分析我们就可以发现,影响的GPU性能的无非就是2大方面,一方面是顶点数量过多,像素计算过于复杂。另一方面就是GPU的显存带宽。那么针锋相对的两方面举措也就十分明显了。

  1. 减少顶点数量,简化计算复杂度。
  2. 压缩图片,以适应显存带宽。

减少绘制的数目

那么第一个方面的优化也就是减少顶点数量,简化复杂度,具体的举措就总结如下了:

  • 保持材质的数目尽可能少。这使得Unity更容易进行批处理。
  • 使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
  • 如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
  • 使用光照纹理(lightmap)而非实时灯光。
  • 使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
  • 遮挡剔除(Occlusion culling)
  • 使用mobile版的shader。因为简单。

优化显存带宽

第二个方向呢?压缩图片,减小显存带宽的压力。

  • OpenGL ES 2.0使用ETC1、PVRTC格式压缩等等,在打包设置那里都有。
  • 使用mipmap。

MipMap

这里匹夫要着重介绍一下MipMap到底是啥。因为有人说过MipMap会占用内存呀,但为何又会优化显存带宽呢?那就不得不从MipMap是什么开始聊起。一张图其实就能解决这个疑问。

这是一个mipmap 如何储存的例子,左边的主图伴有一系列逐层缩小的备份小图

///

GPU的瓶颈主要存在在如下的方面:

  1. 顶点数量过多,像素计算过于复杂
  2. GPU的显存带宽

那么我们要做的就是

  • ①减少顶点数量,简化计算复杂度
  • ②压缩图片,以适应显存带宽。

一、减少绘制的数目

1.1 OverDraw优化

1.1.1. EmptyRaycast

UGUI中 Alpha=0的不可见Image参与Raycast,譬如在屏幕空白处点击的响应,然而这些元素虽然在屏幕上不可见、但依然参与了绘制! 用EmptyRaycast 作为点击事件的响应媒介,可以减少绘制,减少OverDraw
using UnityEngine;
using System.Collections;
namespace UnityEngine.UI
{
    public class Empty4Raycast : MaskableGraphic
    {
        protected Empty4Raycast()
        {
            useLegacyMeshGeneration = false;
        }
        protected override void OnPopulateMesh(VertexHelper toFill)
        {
            toFill.Clear();
        }
    }
}

1.1.2. Fill center

如果texture是中心镂空且切图为九宫格时,可以去除fill center,以减少OverDraw。

1.1.3. 粒子优化

  1. 优化粒子属性。关闭阴影,关闭光照;若可以去掉纹理的Alpha通道,并关闭Alpha Blend和Alpha Test;
  2. 禁用粒子的高级特效。如模型粒子/模型发射器/粒子碰撞体等。
  3. 用最少的粒子效果器。关闭不必要的粒子效果器,采用简单的方式代替。
  4. 控制粒子的材质数量。一个特效通常包含了若干个粒子系统,它们尽量使用内置材质,使用相同的材质实例。
  5. 控制粒子的尺寸和贴图大小。可以粒子的尺寸,可以减缓过绘制,控制贴图大小,可以减少带宽和提高渲染性能。
  6. 制定粒子特效美术规范。下面是游戏Z的粒子规范。
单个粒子的发射数量不超过50个。
减少粒子的尺寸,面积越大就会消耗更多的性能。
粒子贴图必须是2的N次方,尽量控制64x64以内,极少量128x128或256x256,最大不超过256x256。
尽可能去掉粒子贴图的Alpha通道。
尽量不用Alpha Test。
尽量使用已有的材质,提高合并渲染的优化概率。
材质优先用Mobile目录下的材质。
尽可能不用模型做粒子,如果使用,要控制模型面数在100以内,最大粒子数在5以内。
单个特效渲染数据限制:
 小型特效(如受击特效、Buff特效)的面数和顶点数在80以内,贴图在64*64以内,材质数2个以内。
 中型特效(如技能特效)的面数和顶点数在150以内,贴图在128*128以内,材质数4个以内。
 大型特效(如全局特效、大火球)的面数和顶点数在300以内,贴图在256*256以内,材质数6个以内。

1.1.4. 少用Outline,改用Shadow

1.2 使用光照纹理(lightmap)而非实时灯光。

1.3 使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。

1.4 遮挡剔除(Occlusion culling)

二、优化显存带宽

压缩图片,减小显存带宽的压力。
  • 使用ETC1、PVRTC格式压缩等等,在打包设置那里都有。
  • 使用mipmap。


/

在游戏开发过程中,经常遇到一些性能问题,可以通过 Profiler 工具检测,是否是渲染引起的问题。在 Unity 官方网站上找到了优化渲染的进阶教程,于是翻译出来方便阅读。

1.介绍

在这篇文章,我们将会学到当 Unity 渲染一帧图像的时候屏幕后面会发生什么和渲染的时候会遇到什么性能问题以及如何解决渲染相关的性能问题。

阅读文章之前,首先要明白改善渲染性能问题没有放之四海而皆准的方法。我们游戏的渲染性能问题受很多方面的影响,并且高度依赖运行游戏的硬件和操作系统。要记住最有用的一点是,我们通过调查、实验和严格的分析来解决性能问题。

这篇文章包含最常见的渲染性能问题,并提供了如何修复它们的建议及进一步阅读的链接。也存在这种情况,我们的游戏有一个或多个问题这篇文章是没有提及的。这篇文章仍能帮助我们了解我们的问题,并给我们知识和词汇,以便可以有效的寻找解决方案。

2.渲染简介

在此之前,让我们简单快速的看一下 Unity 渲染一帧会发生什么事。理解接下来的事件和正确的术语将对我们理解、研究和努力修复性能问题有帮助。

在这篇文章,我们使用“对象”来代表我们游戏中可能被渲染的对象。任何游戏物品挂载了 Renderer 组件的,将会被当做这样一个对象。

在最基本层面上,渲染可以被描述成这样:

  • CPU 负责什么是必须绘制的及它必须怎样绘制。
  • CPU 发送指令给 GPU。
  • GPU 根据CPU的指令进行绘制。

现在让我们仔细看看到底发生了什么。我们会在文章的后面提及更详细的步骤,但现在我们只理解这些用语,并且明白 CPU 和 GPU 在渲染中起什么作用。

经常使用渲染管线来描述渲染,这是一个非常有用的概念。高效的渲染就是保持信息流畅。

对于渲染的每一帧,CPU 执行了以下的任务:

  • CPU 检测场景中的每个物体,决定他们是否要被渲染。一个物体只有满足一定条件才会被渲染。比如,包围盒的一部分必须在摄像机的视锥之内。被剔除的物体不会被渲染。关于更多的视锥和剔除相关的信息,请点这里
  • CPU 收集每个即将被渲染的物体信息并把数据按命令分类,这就叫做 draw calls 。一个 draw call 包含关于单个 mesh 与该 mesh 如何被渲染的数据。比如,应该使用哪张图片。在某些情况下,物体共享设置可能会被合并成同一个 draw call 。合并两个不同的物体为同一个 draw call 被称为批处理。
  • CPU 为每一个 draw call 创建一个数据包被称为批处理。批处理有时会包含 draw calls 外的数据,但是这种情况不太可能造成性能问题,因此我们在文章中不会考虑这种情况。

对于每个包含 draw call 的批处理来说,CPU 必须执行以下操作:

  • CPU 可以向 GPU 发送一条命令来改变许多被统称为渲染状态的变量。这条命令被称为 SetPass call 。SetPass call 告诉 GPU 下一个要渲染的网格使用哪个设置。只有下一个渲染的网格需要改变前一个网格渲染状态的时候才会发送 SetPass call。
  • CPU 发送 draw call 到 GPU。Draw call 通知 GPU 使用最近使用的 SetPass call 定义的设置去渲染指定的网格。
  • 在某种情况下,批处理可能有多个 pass。pass 是 shader 代码的一部分,一个新的 pass 需要渲染状态的改变。对于批处理中的每个 pass, CPU 必须先发一个新的 SetPass call 然后再发 draw call。

同时,GPU 做以下的工作:

  • GPU 按CPU 发过来的顺序处理任务。
  • 如果当前任务是 draw call, GPU 就渲染网格。这个任务是分阶段的,由 shader 代码分开定义。这部分渲染比较复杂,我们不会详细介绍。但是对于理解一部分叫做顶点着色器的代码告诉 GPU 如何去处理网格的顶点,一部分叫做片段着色器的代码告诉 GPU 如何去绘制单个的像素很有帮助。
  • 这个过程会重复直到 GPU 处理完从 CPU 发过来的所有任务。

现在我们明白了当 Unity 渲染一帧的时发生了什么,让我们考虑一下渲染时可能会出现的问题。

3.渲染问题的类型

理解渲染问题最重要是:渲染一帧,CPU 和 GPU 必须完成它们所有的任务。如果它们当中的任何一个任务完成时间太长,将会造成渲染延迟。

渲染问题有两个基本的原因:第一种是:低效的管道。当渲染管道中当一个或多个任务完成时间耗时过长会造成管道效率低,会中断数据的流程度。管道内的低效率,也被称为瓶颈。第二种是向管道推送了太多的数据。即使是最高效的管道,单帧处理的数据也会有一个上限。

当我们的游戏花费太长时间渲染一帧是因为 CPU 花费太长时间执行渲染任务,那我们的游戏是 CPU 密集。当我们的游戏花费太长时间渲染一帧是因为 GPU 花费太长时间执行渲染任务,那我们的游戏是 GPU 密集。

4.了解渲染问题

在我们做出调整之前,使 用profiling 工具去了解造成性能问题的原因是很重要的。不同的问题需要不同的解决方案。测量我们每次做出的修改的效果也是同样重要的。修复性能问题是一种平衡的行为,修复一方面的问题可能引起另一些问题。

我们将会使用两种工具帮助我们理解和修复渲染性能问题: Profiler 工具和 Fame Debugger 。这两个工具都是 Unity 自带的。

Profiler工具

Profiler 允许我们看到游戏实时执行的数据。我们可以使用 Profiler 看到我们游戏的许多方面的数据,包括内存使用、渲染管道和脚本的性能。

如果你对 Profiler 还不熟悉,请看这个介绍这个教程

帧调试器

帧调试器允许我们看到一帧是如何一步步渲染的。使用帧调试器,我们可以看到每个 draw call 绘制了什么,每个 draw call 的 shader 属性和发送给 GPU 的事件顺序等详细信息。这些信息帮助我们理解我们的游戏是如何渲染的以及我们可以改善那些性能问题。

如果你对使用 Frame Debugger 还不熟悉,请看这个介绍这个教程

查找造成性能问题的原因

在我们尝试改善我们游戏的渲染性能问题之前,我们必须先确定我们游戏运行缓慢是渲染问题造成的。如果造成我们问题的原因是过度复杂的脚本,尝试优化我们的渲染性能是没有意义的。如果你还不确定你的性能问题是否与渲染相关,请看这篇教程

一旦我们确定我们的问题与渲染相关,我们必须了解我们的游戏是 CPU 密集还是 GPU 密集。不同的问题需要不同的解决方案,在尝试修复问题之前,了解造成问题的原因是非常重要的。如果你还不确定你的游戏是 CPU 密集还是 GPU 密集,请查看这篇教程

如果我们确定了我们的问题与渲染相关,并且我们知道我们的游戏是 CPU 密集或 GPU 密集,我们接着往下读。

5.如果游戏是 CPU 密集

从广义上讲,CPU 渲染一帧必须要完成的工作分成三类:

  1. 决定什么是必须绘制的。
  2. 为 GPU 准备命令。
  3. 发送命令给 GPU。

这些宽泛的类别包含许多单个任务,这些任务可以跨多个线程执行。线程允许多个独立的任务同时执行。当一个线程执行一个任务,另一个线程可以执行一个完全独立的任务。这就意味着工作可以更快的完成。当渲染任务被拆分跨多个独立的线程时,这就是多线程渲染。

在 Unity 渲染进程中会调用三种类型的线程:main thread, render thread 和 worker threads。主线程执行我们游戏的主要任务,包括一些渲染任务。渲染线程是一个特殊的线程,负责发送命令给 GPU。每个 worker 线程执行一个单独的任务,比如裁剪或网格蒙皮。哪一个任务被哪一个线程执行依赖于我们的游戏设置和游戏运行的硬件。比如,目标设备的 CPU 的内核越多,产生的 worker 线程越多。因此,我们的游戏在目标设备上分析是非常重要的。我们的游戏在不同的设备执行结果会截然不同。

因为多线程选渲染是复杂且依赖硬件的,在我们尝试改善性能问题之前,我们必须明白哪个任务是造成 CPU 密集的原因。如果我们游戏运行缓慢是因为裁剪操作在一个线程耗时太长了,通过减少另一个发送命令给 GPU 的线程的耗时是没有什么帮助的。

不是所有的平台都支持多线程渲染。在编写这篇文章的时候,WebGL 还不支持这个特性。在一个不支持多线程渲染的平台,所有的 CPU 任务都在同一个线程执行。如果 CPU 密集的运行在这样的平台,CPU 上任何优化都会改善 CPU 的性能。这种情况下,我们应该阅读下面的章节并考虑哪种优化最适合我们的游戏。
图形处理

Graphics jobs 选项在 Player Settings 。它决定 Unity 是否使用 worker 线程执行在 main 线程上或在某些情况下渲染现线程上的任务。在支持该性能的平台上可以提供相当大的性能提升。如果我们希望使用这个特性,我们应该观察使用了 Graphics jobs 和没使用 Graphics jobs 的性能效果。

找到是哪个任务引起的问题

我们可以使用 Profiler 工具确定是哪个任务造成 CPU 密集。本教程展示了如何确定问题所在。

现在我们明白了哪个任务是造成 CPU 密集的原因,让我们看下几种常见的问题和解决方案。
发送命令到 GPU

发送命令到 GPU 的耗时是游戏 CPU 密集最常见的原因。这个任务在大多数平台上是在 render 线程执行,但是在某些平台上(比如PlayStation 4)是在 worker 线程执行的。

发送命令到 GPU 的时候,最消耗的操作是 SetPass call 。如果我们游戏的 CPU 密集是由发送命令到 GPU 造成,减少 SetPass calls 的数量可能是最好的改善性能的方法。

我们在 Unity 的渲染分析器中看到当前有多少 SetPass calls 和 batches 正在发送。在性能不下降的前提下,SetPass calls 数量高度依赖目标硬件。在不降低性能的情况下,一个高端的 PC 机比一个手机能发送更多的 SetPass calls 。

SetPass calls 的数量以及它与 batches 数量的关系,取决于几个因素,我们稍后在文章进行详细的介绍。通常会有以下几种情况:

  • 减少 batches 的数量或使更多的物体共用相同的渲染状态,在大多数情况下会减少 SetPass calls 的数量。
  • 减少 SetPass calls 的数量,在大多数情况下会改善 CPU 的性能。

如果减少了 batches 的数量,没有减少 SetPass calls 的数量,这仍然导致自身性能的改善。这是因为 CPU 处理单个 batch 比处理几个 batches 要高效,即使他们的网格数据同样多。

大体上有三种减少 batches 和 SetPass calls 数量的方法。我们将会更深入的研究每个方法:

  • 减少被渲染的物体数量,可能会减少 batches 和 SetPass calls 的数量。
  • 减少每个必须被渲染的物体的渲染次数,通常会减少 SetPass calls 的数量。
  • 把必须要渲染的物体合并成更少的 batches 将会减少 batches 的数量。

不同的技巧适应不同的游戏,所以我们考虑这里所有的选项,决定哪些在我们的游戏和实验中能够发挥作用。

减少被渲染物体的数量

减少被渲染物体的数量是减少 batches 和 SetPass calls 数量的最简单的方法。这里有几种可以减少渲染物体的方法:

  • 简单地减少我们场景中可见的物体的数量是一个有效的解决方案。比如说,我们在人群中渲染大量的不同的角色,我们可以在场景中简单的放几个角色。如果这个场景看起来还不错并且性能也提高了,这比那些复杂技巧来说是一个更快的解决方案。
  • 我们可以使用摄像机的 Far Clip Plane 属性去减少摄像机的绘制距离。超过这个属性的距离的物体不再被摄像机渲染。如果我们希望掩盖远处不再渲染的物体这个事实,我们可以尝试使用雾去隐藏远处缺失的物体。
  • 基于距离的更细粒度的隐藏物体的方法,我们可以使用摄像机的 Layer Cull Distances 属性为每个单独的层提供自定义的裁剪距离。如果我们游戏有很多的装饰小物件,我们可以使用比大地图更短的裁剪距离去隐藏这些小物件,这是非常有用的。
  • 我们可以使用一个叫 occlusion culling (遮挡剔除)的方法去隐藏那些被遮挡的物体。比如,我们的场景中有一个大的建筑物,使用 occlusion culling 可以关掉被挡住的物体的渲染。Unity 的 occlusion culling 并不适用于所有场景,可能会导致 CPU 消耗过大,并且设置可能会复杂,但是在一些场景上可以极大的改善性能问题。这篇文章针对这一主题进行描述。除了使用 Unity 的 occlusion culling 之外,我们也可以使用自己的方式去手动隐藏我们知道不会展示给玩家的物体。比如,我们场景中包含一些切场景前或切场景后不可见的物体,我们应该隐藏它们。使用我们游戏的知识总是比要求 Unity 动态解决问题更有效。

减少每个物体被渲染的次数

实时灯光、阴影和反射为游戏增加了很多真实性,但是这非常耗性能。使用这些特性可以导致物体被渲染多次,这会大大的影响性能。

这些特性的确切的影响取决于我们为游戏选择的渲染路径(rendering path)。渲染路径是绘制场景时计算的顺序的术语,渲染路径的主要差别在于它们如何处理实时灯光、阴影和反射。一般来说,如果我们的游戏运行在高端硬件上,并且使用了一些实时灯光、阴影、反射,那么延迟渲染( Deferred Rendering )可能是一个更好的选择。如果我们的游戏运行在低端设备上,并且没有使用这些特性,那么正向渲染( Forward Rendering )可能更合适。这是一个非常复杂的问题,如果我们希望使用好实时灯光、阴影、反射,最好是研究这个主题并实验。这个 Unity 手册介绍了不同的渲染路径的信息。这个教程包含了一些有用的 Unity 灯光的主题信息。

不管选择什么渲染路径,使用实时灯光、阴影和反射会影响我们游戏的性能,明白如何优化它们是很重要的。

  • Unity 的动态灯光( Dynamic lighting )是一个非常复杂的主题,深入的讨论这个主题超出了本文的范围,这个教程是对这一个主题有极好的介绍。这篇手册包含了常见的灯管优化细节。
  • 动态灯光非常昂贵。当我们场景包含不移动的物体时,我们可以使用一个叫做烘焙( baking )的技术,预先计算场景的灯光,这样运行的时候就不需要计算灯光了。这篇教程介绍了这个技术,这篇章节提及烘焙灯光的细节。
  • 如果我们希望在游戏中使用实时阴影,这可能是我们可以改善性能的地方。这篇文章展示了如何调整阴影属性(Quality Settings)和它如何影响性能。比如,我们可以使用阴影距离( Shadow Distance )属性确保只有靠近的物体才会产生阴影。
  • 反射探头( Reflection probes )创建了真实的反射,但是批量使用的话,消耗会比较大。最好少使用反射,尽可能优化使用的地方,性能不容忽视。这篇文章展示如何优化反射探头。

减少批处理数量

当条件满足的情况下,一个批处理包含的数据可以为多个物体使用。批处理要满足以下条件:

  • 共享相同材质的相同实例。
  • 拥有唯一的材质设置(如图片,shader,shader 参数等)
     

批处理可以改善性能,尽管和其他所有优化技巧一样,我们必须细心的分析确保批处理的消耗不会超过性能的获取。

以下有几个不同的技巧:

  • 静态批处理( Static batching )是一个允许 Unity 对周边的满足批处理条件的静止不动的物体进行批处理的技术。可以从静态批处理受益的物体,一个很好的例子就是一堆相似的物体,比如石头。这篇教程介绍了静态批处理的设置方法。
  • 动态批处理( Dynamic batching )是另一个允许 Unity 对满足批处理条件的物体进行批处理的方法,不管这些物体是静止还是移动。这种方法对物体是有一定限制的。这篇文章罗列了限制条件。动态批处理对 CPU 性能有一定影响,可能会造成消耗的 CPU 比节省的要多。在使用这一技术时,我们应该牢记这一代价,谨慎使用。
  • 批处理 Unity 的 UI 元素稍微复杂一些,因为它会受到 UI 布局的影响。这个视频进行了概括。这篇文章提供了如何确保 UI 批处理按我们希望的方式运行的深度信息。
  • GPU 实例( GPU instancing )是一种对大量相同物体进行高效批处理的方法。这种方法有一定的限制,并且不是所有设备都支持,但如果我们的游戏屏幕会同时出现许多相同的物体,使用这个方法将会受益良多。这篇文章包含了对 GPU 实例的介绍和如何使用的信息以及平台支持情况与能改善游戏的情况。
  • 纹理图集( Texture atlasing )是把多张图片合并到一张更大的图片的方法。通常在 2D 游戏和 UI 系统上使用,但也同样适用于 3D 游戏。如果我们使用这个技术的时候,我们可以确保物体共享纹理,因此可以进行批处理。Unity 自带图集的工具,叫做 Sprite Packer ,用于 2D 游戏。
  • 可以通过 Unity Editor 或者通过代码手动合并共享材质与纹理的网格。使用这种方式进行合并的时候,我们要意识到阴影、灯光和裁切都是按 per-object 级别运行的。这意味着合并网格的增加的性能可以和那些不再被裁切的物体的性能相抵消。如果我们希望研究这个方法,我们应该测试 Mesh.CombineMeshes 这个函数。例子可以在这里找到。

裁切,排序和批处理

裁切,收集将要绘制的物体的数据,将这些数据排序成 batches 并生成 GPU 命令,这些操作都可能会导致 CPU 密集。

这些任务会在 main thread 线程执行或单个 wroker 线程执行,这取决于我们的游戏设置和目标设备。

  • 裁切本身不太消耗性能,但减少不必要的裁切可能会改善性能。场景中的物体都有每物体每相机( per-object-per-camera )的开销,即使在那些不显示的层( layers )。为了减少这些消耗,我们应该禁用当前不使用的摄像机或隐藏当前不渲染的物体。
  • 批处理可以很大的提高发送命令给 GPU 的速度,但在有时候在某些地方可能会增加不必要的性能开销。如果批处理操作是造成我们游戏 CPU 密集的原因,我们可以在游戏中限制手动或自动批处理的操作。

网格蒙皮

当我们使用一个叫网格动画的技术来变形网格的时候,SkinnedMeshRenderers 就会被用到。通常使用在角色动作上。渲染蒙皮网格的相关任务一般在 main thread 或单个 worker threads 上执行,这取决于我们游戏的设置和目标设备。

渲染蒙皮网格可能是一个很消耗性能的操作。如果我们在 Profiler 窗口发现渲染蒙皮网格是造成 CPU 密集的原因,那我们可以通过以下几个方法来改善性能:

  1. 我们应该考虑当前使用的物体,是否每一个都要使用 SkinnedMeshRenderer 件。有可能我们导入的模型实际不会播放动画却使用了 SkinnedMeshRenderer 组件。像这种情况,使用 MeshRenderer 组件代替 SkinnedMeshRenderer 组件有助于提升性能。当导入模型的时候,我们在模型导入设置中 the model's Import Setting )选择不导入动画,那么这个模型会有 MeshRenderer 组件而不是 SkinnedMeshRenderer 组件。
  2. 如果我们只是在某些时候(比如在开始的时候或在摄像机一定距离内)播放动画,我们可以切换网格到精简版(a less detailed version)或把 SkinnedMeshRenderer 组件替换成 MeshRenderer 组件。 SKinnedMeshRenderer 组件有一个 BakeMesh 函数,它可以创建一个姿势匹配的网格,这在不同的 meshes 或 renderers 切换是非常有用的,并且这种切换对其物体没有任何可见的变化。
  3. 这篇文章包含了优化蒙皮网格动画的建议,这个教程包含了 SkinnedMeshRenderer 组件对改善性能的调整。除了建议之外,牢记顶点数越多,网格蒙皮消耗越大。因此使用更少顶点的模型以减少必须完成的工作量。
  4. 在某些平台上,蒙皮可能是在 GPU 处理的而不是在 CPU 。如果我们的 GPU 有很多内存的话,这个选项是值得尝试的。我们可以在 Player Settings 为当前平台启用 GPU skinning 和质量指标

与渲染无关的主线程操作

很多跟渲染无关的 CPU 任务会发生在主线程,明白这一点很重要。这意味着,如果我们主线程是 CPU 密集,我们减少 CPU 在非渲染任务上的耗时可能可以改善性能。

比如,我们游戏主线程在某些时候执行耗时的渲染操作和运行耗时的用户脚本造成了 CPU 密集。如果我们已经在尽可能不失真的情况下优化渲染操作,我们可能要减少用户脚本的消耗以改善性能。

6.如果我们的游戏是 GPU 密集

如果我们的游戏是 GPU 密集,首先要找出造成 GPU 瓶颈的原因。GPU 性能问题通常由填充率( fill rate ) 受限制引起的,特别是手机设备上,但内存带宽些问题并了解是什么原因造成的、分析并修复这些问题。
填充率

填充率指是 GPU 每秒在屏幕上渲染的像素数量。如果我们游戏是填充率受限制,这意味着我们游戏尝试绘制的像素超过 GPU 每帧能处理的像素。

很容易检测我们游戏 GPU 密集是否是由填充率造成的:

  1. 使用 Profiler 工具,并注意查看 GPU 时间。
  2. 在 Player Settings 降低分辨率。
  3. 再次使用 Profiler 工具,如果性能有所改善,那填充率很可能是造成问题的原因。

如果填充率是造成问题的原因,下面有几个方面可能能帮助我们修复问题。

  1. 片段着色器是 shader 代码的一部分,它告诉 GPU 如何绘制单个像素。GPU 为每个必须绘制的像素执行这段代码,如果代码效率不高,性能问题很容易累积起来。复杂的片段着色器很经常造成填充率问题。
  • 如果我们游戏使用内置的 shaders,我们应该尽可能使用最简单的,最优化的 shaders 达到我们想要的视觉效果。比如,the mobile shader 是高度优化过的 shader 。我们可以使用它们来测试,在不影响游戏视觉的情况下能否改善性能。这些 shader 为手机平台设计,但适用于任何项目。在非手机平台上使用 "mobile" shaders 来提高性能,如果视觉保真能够达到要求,那真是非常好的。
  • 如果我们的游戏物体使用了 Unity 的 Standar Shader ,Unity 会根据当前材质的设置来编译 shader 。只有正在使用的特性会被编译。这意味着移除一些特性,如 detail maps,可以减少片段着色器的复杂度,大大的提升性能。如果我们游戏是这种情况,我们可以尝试修改设置,看看能否在不影响视觉质量的情况下改善性能。
  • 如果我们的项目使用了自定义的 shader,我们应该尽可能的优化它们。优化 shader 是一个比较复杂的主题,这个教程包含了优化 shader 的有用的信息。
  1. 过度绘制(Overdraw)是同一像素绘制多次的术语。当物体被绘制到其他物体顶部的时候会发生 Overdraw ,并且可能引起填充率问题。为了弄明白 Overdraw ,我们必须了解 Unity 在场景绘制物体的顺序。一个物体的 shader 通常使用 render queue 决定了绘制顺序(draw order)。Unity 使用这些信息来严格的绘制物体,这篇文章有更详细的细节。此外,不同的渲染队列在绘制之前进行了不同的排序。比如,Unity 在 Geometry 队列中使用 front-to-back 的排序方式来减少 overdraw ,但在 Transparent 队列则使用 back-to-front 的排序方式来达到所需要的视觉效果。实际上在 Transparent 队列 back-to-front 的这种排序对 overdraw 有最大的影响。Overdraw 是一个复杂的主题,找不到一种可以解决所有overdraw 问题的方法。但是减少 Unity 不能自动排序的重叠物体是很关键的。开始调查这个问题的最好地方实在 Unity 的场景视图。那里有个绘制模式(Draw Model),从那里我们可以看到场景 overdraw,我们便知道从哪里入手减少 overdraw。最常见的过度 overdraw 罪魁祸首是透明材质、没有优化过的粒子特效、覆盖的UI元素,所以我们应该尝试优化或减少它们。这篇文章主要关注 Unity UI,也包含对 overdraw 的一些引导。
  2. 图片特效的使用对填充率问题有一定的影响,特别是当我们使用不止一个图片特效。如果我们游戏使用了图片特效,并且被填充率问题困扰的时候,我们尝试使用不同的设置或更加优化的图片特效(比如Bloom(optimized)代替 Bloom)。如果我们游戏在同一个摄像机使用了超过一个图片特效,会造成多个 shader pass。这种情况下,将我们的图片特效 shader 代码合并成一个pass可能会有益处,这里有个教程。如果我们优化过图片特性后仍然有填充率问题,我们可能需要考虑禁用图片特效,特别是在低端设备上。

内存带宽(Memory bandwidth)

内存带宽是指 GPU 在专用存储器的读写速率。如果我们游戏被内存带宽限制,通常意味着我们使用的纹理太大以至于 GPU 无法快速处理。

检测游戏是否存在带宽问题,我们需要以下几个步骤:

  • 使用 Profiler 工具,并注意查看 GPU 时间。
  • 在 Quality Settings 降低纹理的品质(Texture Quality)。
  • 再次使用 Profiler 工具查看 GPU 时间,如果性能有所改善,那内存带宽很可能是造成问题的原因。

如果我们的游戏存在内存带宽问题,我们需要减少游戏中图片(Texture)内存的使用。我们可以通过下面几个方法优化我们的纹理:

  • 图片压缩(Texture compression)可以极大的减少纹理在硬盘和内存的大小。如果内存带宽是我们游戏中问题,使用图片压缩可以减少图片内存大小来提升性能。Unity 有很多不同的图片压缩格式和设置,每张图片都可以单独设置。一般来说,应该尽可能的使用某种形式的图片压缩。反复实验为每张图片找到最好的压缩格式。这篇文章包含了不同的压缩格式与设置的有用信息。
  • Mipmaps 是图片的低分辨率版本,可以用在远处的物体上。如果场景中有距离摄像机很远的物体,可以使用 mipmaps 来缓解内存带宽问题。场景视图中的 The Mipmaps Draw Mode 可以看到那些地方受益于 mipmaps,这篇文章包含了关于图片开启 mipmaps 的信息。

顶点处理

顶点处理是指 GPU 必须在网格中渲染每个顶点的工作。顶点处理的消耗受两个地方的影响:必须渲染的顶点数量和每个顶点必须执行的操作数量。

如果我们的游戏确定是 GPU 密集,并且不是填充率造成的,也不是内存带宽造成的,那么可能是顶点处理造成的。如果是这种情况,尝试减少 GPU 必须处理的顶点处理数量,可能会获得性能的提升。

我们可以考虑几种可以帮助我们减少顶点数量或减少我们执行每个顶点的操作数量。

  1. 首先,我们应该致力于减少任何不必要的复杂网格。如果我们使用了在游戏中看不到细节的网格或由于创建错误导致顶点过多的低效网格,这是很浪费 GPU 资源的。减少顶点处理消耗的最简单方法是在 3D 美术工程中创建更少顶点数的网格。
  2. 我们可以尝试使用一种叫做 normal mapping(法线贴图) 的方法,在创建更大的几何复杂度网格时候可以使用到。尽管有些 GPU 开销比较大,但在多数情况下会带来性能的提升。这篇文章是在网格中使用法线贴图模拟复杂几何的有用指南。
  3. 如果游戏中的网格没有使用法线贴图,我们通常可以在 import settings 把 vertex tangents(顶点切线) 禁用。这会减少为每个顶点发送给 GPU 的数据数量。
    4.Level of detail(细节层级)也被称为 LOD,是一个优化技术,远离摄像机的网格会降低复杂度。这减少了 GPU 必须渲染的顶点数量,而不影响游戏的视觉质量。这篇文章包含了如何设置 LOD 的更多信息。
  4. Vertex shaders(顶点着色器)是 shader 代码的一段,告诉 GPU 如何渲染每个顶点。如果我们的游戏受限于顶点处理,减少我们的顶点着色器的复杂度可能会有帮助。
  • 如果我们游戏使用了自带的 shaders,我们应该尽可能使用最简单最优化的shaders来达到我们想要的效果。比如,the mobile shader 是高度优化过的 shader。我们可以使用它们来测试,在不影响游戏视觉的情况下能否改善性能。
  • 如果我们的项目使用了自定义的 shader,我们应该尽可能的优化它们。优化 shader 是一个比较复杂的主题,这个教程包含了优化 shader 的有用的信息。

7.总结

我们已经学习了 Unity 的渲染工作,渲染时会出现什么问题以及如何改善我们游戏的渲染性能。使用这些知识和分析关系,我们可以修复渲染相关的性能问题来使我们的游戏有一个更加平滑高效的渲染管线。

本文尽量保持跟原文排版一致,翻译能力有限,以下是官方源地址:https://unity3d.com/cn/learn/tutorials/temas/performance-optimization/optimizing-graphics-rendering-unity-games?playlist=44069

若外部链接失效,csdn搜索公众号名,希望本文对你有帮助,欢迎关注公众号:hellokazhang,一个不给自己设限的终身学习者。

推荐阅读:

【Unity】优化垃圾回收

【Unity】优化代码

http://weixin.qq.com/r/TER9Zd7EsYPOrZoG9xFe (二维码自动识别)

///

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值