Unity中的渲染优化技术

Unity中的渲染优化技术。

影响性能的因素:

对于一个游戏来说,主要适用两种计算资源:CPU和GPU。它们会相互合作,来让我们的游戏可以在预期的帧率和分辨率下工作,其中CPU负责保证帧率,GPU负责分辨率相关的的一些处理。

性能瓶颈:

  1. CPU:
    • 过多的draw call
    • 复杂的脚本或者物理模拟
  2. GPU:
    • 顶点处理
      • 过多的顶点
      • 过多的逐顶点计算
    • 片元处理
      • 过多的片元(由分辨率造成或者由overdraw造成)
      • 过多的逐片元计算
    • 带宽
      • 使用了尺寸很大且未压缩的纹理
      • 分辨率过高的帧缓存

优化技术:

  1. CPU优化:
    • 使用批处理减少draw call数目
  2. GPU优化:
    • 减少要处理的顶点数目:
      • 优化几何体
      • 使用模型的Level of Detail 技术
      • 使用遮挡剔除(Occlusion Culling)技术
    • 减少需要处理的片元数目:
      • 控制绘制顺序
      • 警惕透明物体
      • 减少实时光照
    • 减少计算复杂度
      • 使用Shader的LOD技术
      • 代码方面的优化
  3. 节省内存带宽:
    • 减少纹理大小
    • 使用分辨率缩放

CPU和GPU做了什么:

在了解优化渲染前,我们需要了解在unity中,每一帧的渲染CPU和GPU都做了些什么:

  1. CPU检查场景中每个对象,决定他们是否应该被渲染。(这些对象只有满足一定的条件才会被渲染。)
  2. CPU收集即将被渲染的对象信息,并把这些信息分类为渲染指令(也就是draw calls,我们在之前的文章中也有提到,Draw Call实际上就是一个命令)。一个draw call包含网格数据以及网格如何被渲染。在某些场景,共享设置的一些对象可能会被合并为一个draw call。合并不同对象的数据到同一个draw call被称作batching。
  3. CPU给每个draw call创建一个数据包,称为batch。每一个batch必须包含一个draw call。
  4. CPU会发出指令,使GPU改变一些渲染状态。这个指令被称为SetPass call。SetPass call通知GPU,如何去渲染下一个网格。只有在渲染下一个网格时,其渲染状态相对于渲染上一个网格发生了变化时,才会调用SetPass call。
  5. CPU把draw call发送给GPU。draw call通知GPU使用最近的SetPass call去渲染指定的网格。
  6. 有时,batch可能需要不止一个的pass。pass是shader代码的一部分,而新的pass需要改变渲染状态。对于batch中的每个pass,CPU必须发送一个新的SetPass call然后必须要再次发送draw call。
  7. GPU按照CPU发送的指令顺序处理这些指令。
  8. 如果当前任务是SetPass call,那么GPU更新渲染状态。
  9. 如果当前任务是draw call,那么GPU渲染网格。渲染网格发生在很多阶段,不同阶段的shader代码可以定义渲染。其中:顶点着色器vertex shader告诉GPU怎么处理网格的顶点。片元着色器fragment shader告诉GPU怎么绘制单独的像素。
  10. 以上过程会重复执行,直到所有CPU发送的任务都被GPU完成。

CPU渲染优化:

发送命令到GPU花费时间过长是引起CPU限制的最常见的原因,其最耗时的操作是SetPass call。如果CPU限制是由发送命令到GPU引起的,那么降低SetPass的数量通常是最好的改善性能的方式。

  • 降低SetPass call和batches数量,我们通常可以从以下几个方面来进行:
    • 减少要渲染的对象数量,通常可以同时降低SetPass call和batches的数量。
    • 减少每个要渲染的对象的渲染次数,通常可以降低SetPass call
    • 合并要渲染的对象的数据,可以降低batches数量不同的技术适用于不同的项目,我们在项目开发中因该从项目本身需求出发来选择最合适的方法。

减少要渲染的对象数量

  1. 手动减少场景中物体的数量

  2. Occlusion Culling(遮挡剔除):

    遮挡剔除的原理就是当一个物体被其他物体遮挡住,不在摄像机的可视范围内时不对其进行渲染。

  3. 摄像机Clipping Planes:

    我们可以通过摄像机的Clipping Planes 的Far裁剪远端,从而降低摄像机的绘制范围,为了降低性能损耗同时保证游戏质量,Far的值应该合理控制,不要造成不好的游戏体验,或者我们可以用雾来掩盖不被渲染的远端。

减少渲染对象的渲染次数

  1. LightMap(光照贴图)

    Unity灯光默认是实时光照,也就是说物体在灯光下不同位置会产生不同灯光效果,由于动态光源在实时光照下会有大量的Setpass Calls,为了减小Setpass Calls,我们可以烘焙灯光效果,Unity会为我们生成光照贴图,这样大大减少了Setpass Calls。

  2. 阴影

    1. 阴影 (Shadows):此项决定应该使用哪种阴影类型。

      • 硬阴影和软阴影 (Hard and Soft Shadows):硬阴影和软阴影都将得到渲染。
      • 仅硬阴影 (Hard Shadows Only):仅硬阴影 (hard shadows) 将得到渲染。
      • 禁用阴影 (Disable Shadows):没有阴影会被渲染。
    2. 阴影分辨率 (Shadow resolution):阴影可以按以下几种不同分辨率进行渲染: 低 (Low) 、中 (Medium) 、高 (High) 和很高 (Very High)。分辨率越高,处理开销就越大。

    3. 阴影投射 (Shadow Projection):从平行光源投射阴影有两种方法:

      • 紧密配合 (Close Fit) 渲染分辨率更高的阴影,但是如果摄影机移动,这些阴影有时就会有些许摇晃。

      • 稳定配合 (Stable Fit) 渲染分辨率更低的阴影,而阴影不会随摄影机的移动而摇晃。

    4. 阴影距离 (Shadow Distance):从摄影机处可以看见阴影的最大距离。超出此距离的阴影将不会被渲染。

    5. 阴影层叠 (Shadow Cascades):阴影层叠 (shadow cascades) 数可设置为零、二或四。层叠数越高质量越好,但这要以处理开销为代价。

  3. 反射探针:

    反射探头没有很好的优化方法,在我们实际的项目中却常会用到,以创建更真实的反射,但却会增加batches, 所以我们应该在性能消耗较大的场合尽量最小化其使用率。

合并要渲染的对象的数据

  1. 静态批处理和动态批处理:

    • 在Unity中如果动态物体共用相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的。但同时我们注意CPU时间,同一材质的CPU时间明显更高,所以我们要注意增加的CPU时间是否会高于优化节省的时间。
    • 我们进行静态批处理时,其网格会合并,这也是进行静态批处理的一个重要过程,我们需要他们使用同一个网格同一个材质。静态批处理没动态批处理的诸多限制,且不会造成CPU损耗,但会有更高的内存占用。使用静态批处理时,只需要同一个材质且批处理的物体处于静态,同时勾上Static。
  2. 纹理图集:

    纹理图集是把大量的小纹理合并为一张大的纹理图的技术,当我们使用这个技术为游戏创建美术资源时,我们可以确保物体共享同一图集,因此适合合并。Unity内置了图集工具老版本为Sprite Packer,新版本为SpriteAtlas。

GPU渲染优化:

  • 优化GPU渲染问题主要从三个方面来进行,分别是顶点,填充,带宽。我们需要明确这三个方面的概念。
  1. 顶点处理。顶点处理是指GPU需要渲染网格中每一个顶点的工作。
    顶点处理的消耗受两方面影响:必须渲染的顶点数量,以及在每个顶点上要进行的操作数量。

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

  3. 显存带宽。显存带宽是指GPU读写其专用内存的速度。如果我们的游戏速度受限于显存带宽,通常可能是我们使用的纹理太大,以至于GPU无法快速处理。

纹理压缩:

纹理压缩技术可以同时极大的降低纹理在磁盘和内存中的大小。如果是显存带宽的问题,那么使用纹理压缩减小纹理在内存的大小可以帮助改善性能,Unity提供了很多纹理压缩的格式和设置,运用也十分简单,根据不同的需求、机器和场景我们使用的方式也有所不同,具体可以参照Unity 官方文档

Mipmap:

如果我们的场景包含距离摄像机很远的物体,我们可以通过使用mipmap来缓解显存带宽的问题,mipmap的主要作用便是模型的贴图会根据摄像机距离模型的远近而调整不同质量的贴图显示,以达到优化目的。

LOD(Levels of Detail):

LOD与Mipmap类似,根据距离的远近使用不同精度模型,远处选择低精度的模型,近的时候选择高精度模型,这样就可以减少模型上面的顶点和面片数量从而提高性能。
其和Mipmap的差异主要在一Mipmap针对贴图,而LOD针对模型。Mipmap可以自动生成八张精度不同的贴图,而LOD必须由我们自己提供三个不同精度的模型。

Unity 中的渲染分析工具:

渲染统计窗口:Rendering Statistics Window

性能分析器:Profiler

帧调试器:Frame Debugger


参考:

  1. Unity3D性能优化——渲染篇