UnityShader入门精要C16-Unity中的渲染优化技术

本文记录《UnityShader入门精要》第16章的读书笔记。

本章简单介绍了Unity中移动平台的特点(不同设备硬件区别大、参差不齐、对性能要求高),以及影响性能的因素(CPU/GPU/带宽)和它们的优化方法。

16. Unity中的渲染优化技术

一个正确的做法是,从一开始就把优化当成是游戏设计中的一部分。

本章会介绍一些和渲染相关的常见优化技术,例如批处理、LOD(Level of Detail)等。除此之外,还可以避免使用全屏的屏幕特效,避免使用计算复杂的shader,减少透明度混合造成的overdraw等。

16.1 移动平台的特点

移动平台的GPU架构有很大的不同,更专注于尽可能使用更小的带宽和功能。

为了尽可能移除隐藏的表面,减少overdraw(一个像素被绘制多次),不同芯片的做法:

  • PowerVR芯片(通常用于IOS设备和某些Android设备):基于瓦片的延迟渲染(Tiled-based Deferred Rendering, TBDR)
  • Adreno(高通的芯片)和Mali(ARM的芯片):使用Early-Z或相似的技术进行低精度的深度检测
  • Tegra(英伟达的芯片)等:使用了传统的架构设计

尤其是在Android平台上,不同设备的硬件区别很大。

16.2 影响性能的因素

对于一个游戏来说,主要需要使用两种计算资源:CPU和GPU,影响帧率分辨率。其中CPU主要负责保证帧率,GPU主要负责分辨率相关的处理。因此,造成游戏性能瓶颈的主要原因有:

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

对于CPU,限制它的主要是每一帧中 draw call 的数目。调用绘制命令的时候,就会产生一个 draw call。每次调用时CPU都需要改变很多渲染状态的设置,非常费时。其他原因,如物理、布料模拟、蒙皮、粒子模拟等也是计算量很大的操作,但不在本书讨论范围。

对于GPU,它负责整个渲染流水线,因此GPU的性能瓶颈和需要处理的顶点数目、屏幕分辨率、显存等因素有关。相关优化策略可以从减少处理的数据规模、减少运算复杂度等方面入手。

本章后续章节会涉及的优化技术有:

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

16.3 Unity中的渲染分析工具

Unity内置工具包括:

  • 渲染统计窗口(Rendering Statistics Window)
  • 性能分析器(Profiler)
  • 帧调试器(Frame Debugger)

16.3.1 认识Unity5的渲染统计窗口

渲染统计窗口(Rendering Statistics Window)显示当前游戏各个渲染统计变量,可以通过Game视图右上方的菜单中单机Stats按钮来打开它,如下图所示:

包含的信息(这么多年过去了,居然没啥变化):

  1. 音频(Audio)
  2. 网络(Network)(并没有
  3. 图像(Graphics)
    • 每帧的时间和FPS:处理和渲染一帧所需的时间,以及FPS
    • Batches:一帧中需要进行的批处理数目
    • Saved by batching:合并的批处理数目,这个数字表明了批处理为我们节省了多少 draw call
    • Tris和Verts:需要绘制的三角面片和顶点数目
    • Screen:屏幕的大小,以及它占用的内存大小
    • SetPass:渲染使用的Pass数目,每个Pass都需要Unity的runtime来绑定一个新的Shader,这可能造成CPU的瓶颈
    • Visible skinned meshes:渲染的蒙皮网格的数目
    • Animations:播放的动画数目

Unity5渲染统计窗口去掉了 draw call 的显示,如果要查看,可以用性能分析器。

16.3.2 性能分析器的渲染区域

单机Window->Profiler(现在是Window->Analysis->Profiler)打开性能分析器(Profiler)

16.3.3 再谈帧调试器

在之前的章节多次应用过了帧调试器(Frame Debugger)。略。

16.3.4 其他性能分析工具

对于移动平台上的游戏,我们更希望得到真机上运行游戏时的性能数据。

  • Android
    • 高通Adreno
    • 英伟达NVPerfHUD
  • IOS
    • 内置分析器
    • PowerVRAM的 PVRUniSCo shader 分析器
    • XCode中的 OpenGL ES Driver Instruments

一些其他的性能分析工具可以在Unity的官方手册中找到:https://docs.unity3d.com/Manual/MobileProfiling.html

除此之外,现在还有UPR可用来进行全平台的性能分析:https://upr.unity.com/

16.4 减少 draw call 数目

最常见的是批处理(batching),实现原理是为了减少每一帧需要的 draw call 数目,每次调用 draw call 时尽可能的处理多个物体。

什么样的物体可以一起处理:使用同一个材质的物体。它们之间的不同仅仅在于顶点数据的差别。

Unity支持两种批处理方式:

  • 动态批处理:优点是Unity自动完成,并且物体可移动。缺点是限制很多,一不小心就失效了。
  • 静态批处理:优点是自由度很高,限制很少。缺点是可能会占用更多的内存,而且经过静态批处理后的物体都不可以再移动了。

16.4.1 动态批处理

如果场景中有一些模型共享了同一材质并满足一些条件,Unity自动把它们进行批处理。动态批处理的基本原理是,每一帧把可以进行批处理的模型网格进行合并,再把合并后的模型数据传递给GPU,然后使用同一个材质对其进行渲染。

需要满足条件:

  1. 顶点属性规模(顶点位置、法线和纹理坐标)小于900,及顶点数目小于300。这个数字未来可能会变化。
  2. 所有对象的需要使用同一个缩放尺度。但Unity5已经不限制这个了。
  3. 使用光照纹理(lightmap)的物体需要小心处理。为了让这些物体可以被动态批处理,需要保证它们指向光照纹理中的同一个位置
  4. 多Pass的Shader会中断批处理。

搭建了3个立方体1个球的场景,其中3个立方体使用同一个材质,1个球使用另一种材质。环境光关闭了阴影。然而,Batch数目是5,似乎没有进行动态批处理!

打开帧调试器查看原因。球体和第一个立方体是新的材质,不会进行动态批处理,这符合预期。后面几个使用相同材质的立方体,有写明不能和前一个进行合批的原因:

Why this draw call can’t be batched with the previous one

Dynamic Batching is turned off in the Player Settings or is disabled temporarlly in the current context to avoid z-fighting

翻译过来:原因是动态批处理在PlayerSettings中被关闭了,或者是在当前环境中被禁用了以避免z-fighting。

打开PlayerSettings,确实动态批处理没有启用,现在手动启用它。如此一来动态批处理就生效了,现在Batches数目是3,Saved by batching 是2,因为批处理节省了2个 draw call。


在动态批处理启用的情况下,再增加一个点光源。此时Batches数目是9,而 Saved by batching 变为了0,说明了批处理未生效。这是因为多个Pass的Shader在应用多个光照的情况下,破坏了动态批处理的机制,无法批处理了。

这里有个问题,为啥Unity默认情况下,是不启用动态批处理呢?

16.4.2 静态批处理

相比动态批处理,静态批处理适用于任何大小的几何模型。实现原理是,只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中,这意味着这些模型不可以在运行时刻被移动。

它的缺点是往往需要更多的内存来存储合并后的几何结构。解决方法是要么忍受这种牺牲内存换取性能的方法,要么不用静态批处理改用动态批处理,或者自己编写批处理方法。(现在硬件比较好,内存大了很多,是不是静态批处理的缺点会变小

使用这样的场景,1个关闭了阴影的平行光,3个相同材质的茶壶,1个另外材质的立方体。Statistics中显示Batches是5,Saved by batching 是0,没有使用动态批处理。查看帧调试器,它说明了为什么没有使用动态批处理:茶壶的顶点数超过了300。

将物体面板上的Static复选框勾选上,就可以实现静态批处理。

此时Statistics中的批处理数目没有发生变化。但运行后Batches变成了3,Saved by batching 是2。

在运行时查看每个模型使用的网格,变成了名为 Combined Mesh (root:scene) 的东西。其中的每个物体变成了它的submesh。

下面是Unity分析器(Profiler),点击了Rendering后显示的统计数据。左边是不使用静态批处理,右边是使用了静态批处理的数据。可见使用了静态批处理后,RenderTextures和 VRAM usage 有少许增加,但感觉影响不大?(原书中是对 VBO total 进行了比较,然而我这都是0)

如果场景中包含了平行光以外的光源,并且在Shader中定义了额外的Pass来处理它们,这些额外的Pass是不会被批处理的。但处理平行光的BasePass依然会被静态批处理。

16.4.3 共享材质

以上知道了无论是动态批处理还是静态批处理,都要求模型之间材质相同。但不同模型之间总会有不同的渲染属性,例如纹理、颜色等。所以需要一些策略来尽可能合并材质:

  • 如果只是纹理不同,可以把这些纹理合并到一张更大的纹理中,这个称为图集(atlas)。再使用不同的采样坐标对纹理采样。
  • 除了纹理不同,还有其他微小的参数变换,例如颜色、某些浮点属性。一种常用的方法是使用网格的顶点数据来存储参数(顶点颜色)

如果要在脚本中访问共享材质,应使用 Renderer.sharedMaterial 来保证修改的是和其他物体共享的材质,而不是 Renderer.material。

16.4.4 批处理的注意事项

在选择使用动态批处理还是静态批处理时,建议:

  • 尽可能选择静态批处理,但要时刻小心对内存的消耗,并且记住经过静态批处理的物体不可以再被移动。
  • 如果无法进行静态批处理,而使用动态批处理,要小心它的条件限制。
  • 对于游戏中的小道具,如可以拾取的金币等,可以使用动态批处理。
  • 对于包含动画的物体,无法全部使用静态批处理,但其中如果有不动的部分,可以标识成 Static。
  • 因为批处理需要把多个模型变换到世界空间下再合并它们,如果 Shader 中存在基于模型空间下的坐标运算,会得到错误的结果。一个解决方法是在 Shader 中使用 DisableBatching 标签来强制使该 Shader 的材质不会被批处理。
  • 半透明材质通常需要使用严格的从后往前的绘制顺序来保证透明混合的正确,这些物体 Unity 会首先保证它们的绘制顺序,再尝试进行批处理。所以当绘制顺序无法满足时,批处理无法在这些物体上被成功应用。

16.5 减少需要处理的顶点数目

16.5.1 优化几何体

在建模时要记住:尽可能减少模型种三角面片的数目,一些对于模型没有影响、或是肉眼非常难察觉到区别的顶点都要尽可能去掉。

在Unity的渲染统计窗口,可以看到渲染当前帧需要的三角面片数目和顶点数目,往往是多于建模软件里显示的。这是因为三维软件中组成几何体的每个点就是单独的一个点,但在Unity中GPU看来,有时需要把一个顶点拆分成两个或更多的顶点。原因是 分离纹理坐标(uv splits)产生平滑的边界(smoothing splits)

另外,建议移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离。

16.5.2 模型的LOD技术

使用 LOD(Level of Detail) 技术。

在 Unity 中使用 LOD Group 组件为物体构建 LOD。为同一个对象准备多个不同细节程度的模型,然后把它赋给 LOD Group 组件中的不同等级。

16.5.3 遮挡剔除技术

遮挡剔除(Occlusion culling)。消除那些在其他物件后面看不到的物件,提升性能。和相机的视锥体剔除(Frustum Culling)不同。

使用遮挡剔除的步骤详见 Ref2。

16.6 减少需要处理的片元数目

重点在于减少 overdraw,即同一个像素被绘制了多次。

在 Scene 视图左上方的下拉菜单中选中 Overdraw,切换成可以查看 overdraw 的视图,可以看物体相互遮挡的层数。

16.6.1 控制绘制顺序

控制绘制顺序。由于深度测试的存在,我们可以保证物体都是从前往后绘制。

尽可能的把物体的队列设置为不透明物体(opaque)(如 Background Geometry AlphaTest)。避免使用半透明队列(Transparent Overlay),因为它们是从后往前绘制的。

16.6.2 时刻警惕透明物体

因为半透明对象没有开启深度写入,如果要正确渲染,必须从后往前。这意味着半透明物体几乎一定会造成 overdraw。

对于占据屏幕多的半透明 GUI,可以尽量减少 GUI 所占面积。可以将把 GUI 的绘制和三维场景的绘制交给不同的相机。

在移动平台上,透明度测试会影响性能,因为使用的 discard 或 clip 等操作,会导致一些硬件的优化策略失效,如 PowerVR 使用的基于瓦片的延迟渲染技术。这时透明度混合的性能往往比透明度测试好。

16.6.3 减少实时光照和阴影

实时光照对于移动平台是一种非常昂贵的操作。

有这些方法,可以减少实时光照和阴影:

  1. 通过烘焙技术,将光照提前烘焙到一张光照纹理(lightmap)中,然后在运行时刻只需要根据纹理采样得到光照结果
  2. 使用 God Ray 模拟光源,不是真的光源,通过透明纹理模拟
  3. 一定要用更多的实时光的话,可以用逐顶点光照代替
  4. 把复杂光照计算存储到一张查找纹理(lookup texture,也被称为查找表,lookup table,LUT)。在运行时,使用光源方向、视角方向、法线方向等参数对 LUT 采样
  5. 使用烘焙把静态物体的阴影信息存储到光照纹理中,只对场景中的动态物体使用适当的实时阴影

16.7 节省带宽

大量使用未经压缩的纹理以及使用过大的分辨率,都会造成由于带宽而引发的性能瓶颈

16.7.1 减少纹理大小

  1. 所有纹理的长宽比最好是正方形,且长宽值最好是2的整数幂
  2. 使用多级渐远纹理技术(mipmapping),在距离物体很远时,会使用更小、更模糊的纹理来代替
  3. 纹理压缩,不同平台格式不同,Unity会自己选用

16.7.2 利用分辨率缩放

尤其对于很多低端手机,除了分辨率高其他硬件条件并不好。因此可能需要对于特定机器进行分辨率的缩放。

Unity 中设置屏幕分辨率可以直接调用 Screen.SetResolution

16.8 减少计算复杂度

16.8.1 Shader的LOD技术

和模型的 LOD 技术类似,Shader 的 LOD 技术可以控制使用的 Shader 等级,当 Shader 的 LOD 值小于某个设定的值,才会被使用:

SubShader{
    Tags{ "RenderType" = "Opaque" }
    LOD 200
}

Shader 的默认 LOD 等级无限大,意思是任何被当前显卡支持的 Shader 都可以被使用。

16.8.2 代码方面的优化

通常来说,游戏需要计算的数目排序是:对象数<顶点数<像素数。

普遍成立的优化策略:

  1. 尽可能使用低精度的浮点值进行运算
    1. float/highp 适用于存储顶点坐标等
    2. half/mediump 适用于标量、纹理坐标
    3. fixed/lowp 适用于颜色变量和归一化的方向矢量。避免对低精度变量使用频繁的 swizzle 操作(如 color.xwxw)
    4. 避免不同精度之间的转换
  2. 使用插值寄存器把数据从顶点着色器传递到下一个阶段时,尽可能使用少的插值变量
  3. 少使用全屏的屏幕后处理效果。
  4. 代码优化规则
    1. 少用分支语句和循环语句
    2. 少用类似 sin/tan/pow/log 等较为复杂的数学运算,用查找表来替代
    3. 少用discard操作,会影响硬件的某些优化

16.8.3 根据硬件条件进行缩放

移动设备性能千差万别。

一个非常简单且使用的方式是放缩(scaling)思想。先保证游戏最基本的配置在所有平台上运行良好,对于更好的设备,可以使用更高的分辨率、开启屏幕后处理、粒子效果等。

16.9 扩展阅读

  1. Unity官方手册的移动平台优化实践指南,针对移动平台的优化技术,包括渲染和图形方面的优化,脚本优化等:https://docs.unity3d.com/Manual/MobileOptimizationPracticalGuide.html
  2. 优化图像性能:https://docs.unity3d.com/Manual/OptimizingGraphicsPerformance.html
  3. SIGGRAPH 2011 上 Unity 进行了一个关于移动平台上Shader优化的演讲:https://blogs.unity3d.com/2011/08/18/fast-mobile-shaders-talk-at-siggraph/
  4. Unite 2013,Unity 一个名为针对移动平台优化的演讲
  5. GDC 2014,Unity 展示了如何使用内置的分析器分析移动平台的游戏性能
  6. SIGGRAPH 2015,Unity 进行了一系列的演讲
  7. Unity 和来自高通、ARM等公司的开发人员共同呈现了名为 Moving Mobile Graphics 的课程中,讲解了移动平台上 PBR 的优化技术
  8. 2011年发布在移动平台的第三人称射击游戏《ShadowGun》
  9. Unity 自带的项目《Angry Bots》

999. Ref

  1. 移动平台性能分析工具文档:https://docs.unity3d.com/Manual/MobileProfiling.html
  2. 遮挡剔除:https://docs.unity3d.com/Manual/OcclusionCulling.html
  3. Unity3D研究院之使用Android的硬件缩放技术优化执行效率:http://www.xuanyusong.com/archives/3205

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cdd@ahucd.cn

×

喜欢就点赞,疼爱就打赏

B站 cdd的庇护之地 github itch