Chapter16 渲染优化技术
- 一、移动平台的特点
- 二、影响性能的因素
- 三、Unity中渲染分析工具
-
- [1.渲染统计窗口 Rendering Statistics Window](#1.渲染统计窗口 Rendering Statistics Window)
- 2.性能分析器(Profiler)的渲染区域
- [3.帧调试器 Frame Debugger](#3.帧调试器 Frame Debugger)
- 4.其他性能分析工具
- [四、减少Draw Call的数目](#四、减少Draw Call的数目)
- 五、减少需要处理的顶点数目
-
- 1.优化几何体
- [2.模型的LOD技术 Level of Detail](#2.模型的LOD技术 Level of Detail)
- [3.遮挡剔除技术 Occlusion culling](#3.遮挡剔除技术 Occlusion culling)
- 六、减少需要处理的片元数目
- 七、节省带宽
- 八、减少技术复杂度
-
- [1.Shader 的 LOD 技术](#1.Shader 的 LOD 技术)
- 2.代码优化
一、移动平台的特点
- GPU架构与PC端有很大的不同,移动设备上专注于尽可能使用更小的带宽和功能
- 需要针对不同的芯片平台,进行更有针对性的优化,尤其是在安卓平台上
移除隐藏的表面
- 减少overdraw(即一个像素被绘制多次)
- PowerVR芯片:基于瓦片的延迟渲染 TBDR架构:把所有渲染图元装进一个个瓦片中,再由硬件找到可见的片元,只有可见的片元才会执行片元着色器
- Adreno(高通)或Mali(ARM):Early-Z或相似的技术,进行低精密度的深度检测,来剔除不需要的片元
二、影响性能的因素
- 游戏主要需要使用的计算资源:CPU和GPU------互相合作,让游戏可以在预期的帧率 和分辨率下工作,CPU负责保证帧率,GPU负责分辨率
造成性能瓶颈的原因
CPU
- 过多的Draw Call ------ CPU需要多次改变渲染设置,很耗时
- 复杂的脚本或者物理模拟
GPU
- 顶点处理
- 过多的顶点
- 过多的逐顶点计算
- 片元处理
- 过多的片元(可能是分辨率造成的,也可能是overdraw引起)
- 过多的逐片元计算
- 带宽
- 使用了尺寸很大且未压缩的纹理
- 分辨率过高的帧缓存
三、Unity中渲染分析工具
1.渲染统计窗口 Rendering Statistics Window
- Game窗口 -> Stats
- 每帧的时间和FPS数目:在Graphics右侧
- Batches:一帧中需要进行的批处理数目
- Saved by batching:合并的批处理数目(表明了批处理节省了多少draw call)
- Tris 和 Verts:需要绘制的三角形 和顶点数目
- Screen:屏幕大小以及占内存大小
- SetPass:渲染使用的Pass数量(可能会造成CPU渲染瓶颈)
- Visible skinned meshes:渲染的蒙皮网格数
- Animations:播放的动画数目
2.性能分析器(Profiler)的渲染区域
- Window -> Analysis-> Profiler,rendering区域
3.帧调试器 Frame Debugger
- Window -> Analysis-> Frame Debugger
- 帧调试器的调试面板上显示了渲染这一帧的所有渲染事件
- 本例中渲染数目为13个,其中包含9个Draw Call
4.其他性能分析工具
- 高通的Adreno分析工具:可以对不同的测试机进行详细的性能分析
- 英伟达NVPerHUD:可以得到几乎所有需要的性能分析数据
- PowerVRAM的 PVRUniSCo shder分析器
- 等等
四、减少Draw Call的数目
- 批处理:减少每一帧需要的draw call数目,
- 相同材质的物体可以一起处理,它们之间的不同仅仅再与顶点数据的差别
- 两种批处理方式
- 动态批处理:
- 由Unity自动完成,物体可以移动
- 限制很多
- 静态批处理:
- 自由度高,限制少
- 可能会占用更多的内存,物体不可以移动
- 动态批处理:
1.动态批处理
- 原理:每一帧把可以进行批处理的模型网格进行合并,再把合并后的模型数据传给GPU,然后使用同一个材质进行渲染
- 限制:(随着Unity版本更新会有不同)
- 进行动态批处理的网格的顶点属性规模有限制
- 需要保证物体指向光照纹理中同一个位置
- 多Pass的Shader会中断动态批处理------有时需要额外的Pass来添加更多的光照效果
2.静态批处理
- 原理:只在运行开始阶段,把需要批处理的模型合并到一个新的网格结构中(不能在运行时刻移动),但只需要进行一次合并,所以比动态处理更高效
- 性能瓶颈:当在批处理前一些物体就共享了网格,在内存中每个物体都会对应一个该网格的复制品,即一个网格会变成多个网格发给GPU
- 如果场景中包含了平行光以外的光源,并在Shader中定义了额外的Pass 来处理,这些Pass不会被批处理的
- 使用:勾选物体的Static
3.共享材质
- 使用同一个材质(不是同一个Shader)才可以使用批处理
- 如果想要微调一些参数:使用网格的顶点数据(最常见的是顶点颜色数据)来存储这些参数
- 如果要脚本访问共享数据,应该是 Renderer.sharedMaterial 而不是 Renderer.material
4.批处理注意事项
- 尽可能选择静态批处理,但小心内存消耗
- 小心动态批处理的限制
- 游戏中的小道具可以使用动态批处理
- 包含动画的物体,如果有不动的部分可以标为static
- Shader中如果存在基于模型空间下的坐标运算时,不能使用批处理 DisableBatching标签来让Shader不会被批处理
- 半透明渲染的物体时,由于严格的渲染顺序,批处理可能不成功
五、减少需要处理的顶点数目
1.优化几何体
- 减少三角形面片数量:去除对模型没有影响或肉眼难以察觉的顶点,降低模型复杂度
- 避免硬边和纹理分离: 移除不必要的硬边和纹理衔接,减少顶点拆分,降低顶点数量
2.模型的LOD技术 Level of Detail
- 原理:当一个物体原理摄像机时,LOD减少模型上的面片数量,从而提高性能
- Unity实现:使用 LOD Group 组件为对象设置不同 LOD 等级的模型,Unity 会自动选择合适的模型进行渲染
3.遮挡剔除技术 Occlusion culling
- 用来消除那些在其他物件后面看不到的物件
- 同时减少CPU和GPU的符合,CPU可以提交更少的Draw Call,GPU处理的顶点和片元也更少了
- 实现可以看unity手册相关内容
六、减少需要处理的片元数目
- 这部分优化重点在减少overdraw
1.控制绘制顺序
由于深度测试,物体都是从前往后绘制的,很大程度上减少了overdraw
- 在Unity中,渲染队列数目小于2500("Background""Geometry"和"AlphaTest")的对象都是不透明的,从后往前绘制;但"Transparent""Overlay"等物体是从后往前绘制的
- 利用渲染队列: 将物体分配到合适的渲染队列,确保不透明物体从前向后渲染,避免半透明物体造成 overdraw
- 调整渲染队列顺序 : 根据物体在场景中的位置和重要性,调整渲染队列的顺序,例如先渲染主要角色,再渲染敌方角色和场景物体
2.警惕透明物体
对于半透明物体来说,由于没有开启深度写入,从后往前渲染------几乎一定会造成overdraw
- 减少大面积半透明物体: 减少场景中大面积半透明物体的数量,例如 GUI 元素,避免造成过多的 overdraw
- 优化半透明物体渲染: 在移动平台上,尽量减少透明度测试的使用,改为使用透明度混合,并注意透明物体的绘制顺序
3.减少实时光照和阴影
对于逐像素的光源来说,被这些光源照亮的物体需要再被渲染一次,还不能使用批处理操作(额外的Pass无法批处理)
- 使用烘焙光照 : 将静态物体的光照信息烘焙到光照纹理中,避免实时计算,降低 CPU 和 GPU 负担
- LUT 查找纹理:将光源方向、视角方向、法线方向等参数存储在LUT中。在运行时对LUT采样即可得到光照模型(更复杂的BRDF模型)。主角用更大分辨率的LUT,NPC用小的LUT可以优化性能
- 限制实时光源数量: 减少场景中点光源的数量,避免过多的逐像素光照计算,并使用逐顶点光照代替逐像素光照
- 优化实时阴影: 尽量减少实时阴影的使用,对静态物体使用烘焙阴影,对动态物体使用合适的实时阴影技术
七、节省带宽
1.减少纹理大小
纹理长宽比最好是正方形,且长宽值为2的整数幂
- 纹理图集:将多个纹理合并成一张大图,减少Draw Call 和纹理切换次数
- 多级渐远纹理技术mipmapping :纹理勾选Advanced后,可以勾选 Generate Mip Maps 来生成纹理金字塔,动态选择
- 纹理压缩:将纹理压缩成更小的格式,例如 PVRITC、DXT、ATC 等,节省存储空间和带宽
2.利用分辨率缩放
- 根据设备性能调整屏幕分辨率,例如在低性能设备上降低分辨率,在高性能设备上提高分辨率
- 使用 Screen.SetResolution 函数设置屏幕分辨率
八、减少技术复杂度
1.Shader 的 LOD 技术
只有 Shader 的 LOD 值小于某个设定值,该 Shader 才会被使用,超过设定值的 Shader 将不会被渲染,可以在 SubShader 中设置 LOD 值,或使用 Shader.maximumLOD 或 Shader.globalMaximumLOD 设置允许的最大 LOD 值
2.代码优化
- 尽可能使用低精度浮点值进行运算:例如 half、fixed 等,减少计算时间和内存占用。
- 尽量减少从顶点着色器传递给后续阶段的插值变量数量,例如将多个纹理坐标打包到一个 float4 变量中。
- 避免使用复杂的数学函数:例如 sin、tan、pow、log 等,可以使用查找表替代。
- 分支循环: 尽量避免使用分支语句和循环语句,这些语句会影响 GPU 并行效率。
- discard 操作: 避免使用 discard 操作,因为它会影响 GPU 的一些优化策略。
- 全屏后处理: 尽量避免使用全屏的屏幕后处理效果,例如 Bloom、热扰动等,可以使用低精度运算或查找表优化。
- 合并特效: 将多个屏幕后处理特效合并到一个 Shader 中,减少绘制次数和计算量。
- 硬件缩放: 根据设备性能调整游戏渲染效果,例如在低性能设备上关闭部分特效,在高性能设备上开启更多特效。