unity性能优化方案
1.Unity 性能调试工具及使用方法
2.垃圾回收(Garbage Collection)
3.CPU相关优化
4.GPU相关优化
5.使用UPR(Unity Performance Reporting)云服务进行性能测试
每一帧需要做到的事情
- 渲染一帧所需的时长 > CPU计算所需时长 + GPU计算所需时长
- (比如,如果CPU上的物理计算和脚本运行要花很多时间,那么即使Shader优化的再好,也不会提高帧率;反过来说,如果GPU上压力山大,那么优化物理系统和脚本也对提高帧率没有什么大的帮助。因此,我们要分析找到整个系统中的薄弱环节,也就是漏水的水桶上最低的那一块板。)
CPU 和 GPU 各自负责的工作
- CPU 相关:Skinning,Batching(Static Batching,Dynamic
- Batching等),物理相关,用户脚本,粒子效果等 GPU 相关:Shader,Drawcalls,Image
- Effects(后处理) 但是CPU和GPU不是各自孤立的:
- Draw Call太高 -> 使用CPU做Batching降低Draw Call -> 性能提升
- Draw Call不高 -> 使用CPU做Batching降低Draw Call -> 浪费CPU处理能力,导致culling效率下降 -> 性能下降
优化时使用的环境
对Project Settings进行优化设置
- 通过编辑器顶部菜单Edit/Project Settings打开项目设置界面。找到Player下面的 Other Settings界面。
- 确保Static Batching,Dynamic Batching,GPU Skinning,Graphics Jobs 都已经启用。
- (如果是Oculus Quest)禁用ARMv7,打开ARM64。在Scripting Backend下拉框中选择IL2CPP,这样所有的C#代码会被转成C++代码,提高运行速度。
- (如果是Rift和Rift S)在Scripting Backend下拉框中选择IL2CPP(但是Debug会变得不方便),所以在开发阶段可以使用Mono,正式出包时改成IL2CPP。
- 打开XR Plugin Management设置界面,安装Oculus XR Plugin,确保Initialize on Startup为勾选状态。
- 打开Oculus设置界面,确保Shared Depth Buffer和Dash Support都为勾选状态,然后把Stereo Rendering Mode设置为Single Pass Instanced。
- 在Quality设置界面,确保Android平台下只勾选Medium选项,其他选项都要为未勾选状态;其他平台可以全部勾选。
- 将Pixel Light Count设置为1或者0(如果通过Vertex着色器提供光照信息的话)。
- 把Anti Aliasing 设置为 4x Multi Sampling,在Oculus Quest也可以用,因为对GPU压力不大,但是画面质量可以提升很多。
- 把Shadows 设置为 Disable Shadows 或者Hard Shadows Only。
- 将Blend Weights设置为2 Bones。
- 打开Graphics设置界面,确保Android平台设置为Low或者Medium(Oculus Quest)。
代码相关
CPU 相关优化
- 光照烘焙
- 使用 Occlusion Culling
- 通过 Mesh Baker Free 提升网格资源性能
- GPU Instancing
- Texture Atlasing
- 从脚本中获取材质时要使用 Renderer.sharedMaterial 而不是 Renderer.Material,因为后者会产生一份新的copy,如果原先这份材质是被 Batch 的,那么 Batch 会失效
使用Occlusion Culling(遮挡剔除)
一般降低drawcall通用方法
- 在 Hierarchy 窗口中找到 Main Camera,在Scene窗口用旋转工具对相机进行旋转,我们可以看到不管相机朝向哪个方向,在 Scene 窗口中从顶部往下看,场景中所有的 GameObject 都会被渲染。
- 把 Main Camera 上的 Occlusion Culling 选项勾选。
- 在 Occlusion 界面中点击右下角的 Bake 按钮,稍等片刻完成烘焙。
通过 Mesh Baker Free 插件提升网格资源性能
这个插件可以合并网格
网格合并可能的副作用是:因为合并完的网格过大,导致Occlusion Culling不起作用。
GPU Instancing
- 可用较少的 Draw Call 渲染多个使用同一网格(Mesh)的Game Object,提高渲染性能。
- 适用于渲染建筑物,树林,草地,或者任何在场景中可以重复使用的模型。
- 即使这些 GameObject 拥有不同的颜色,尺寸等变化,只要它们使用的是同一个网格,就可以使用GPU Instancing。
- 也可以在脚本中使用 GPU Instancing API 实现相关功能:
Graphics.DrawMeshInstanced 和 Graphics.DrawMeshInstancedIndirect
Unity 的 Batching 优先级:
- Static Batching > GPU Instancing > Dynamic Batching
-
- 如果 Project Settings > Player 界面中的 Static Batching 勾选,GameObject也被设置为 Batching Static 并且材质的 GPU Instancing 打开:会在 Inspector 中显示一个报警,提示 GPU Instancing 会被禁用。
GPU相关优化
- GPU优化的三个方面:
-
-
-
- Pixel Fill Rate:GPU每秒可以在屏幕上画出的像素数量
-
-
- Texture Fill Rate:GPU每秒可以查看的贴图数量
- GPU Memory Bandwidth(显存带宽):
-
- 每一秒GPU可以传输到显存(VRAM)的数据量(以字节为单位)
- Vertex Processing(顶点处理):
-
-
检查方法
- 在Profiler中查看GPU在渲染上花费的时长
- 在Player Settings中降低分辨率
- 再次运行查看GPU的渲染时长
- 如果性能提升,就说明GPU来不及渲染有 Fill Rate不够的问题
具体优化方法:
- 减少 shader 中的 Fragment Shader 的复杂度,因为 Fragment shader 负责告诉GPU要怎么往屏幕上画像素。
- 使用更优化的 Shader,比如 Mobile Shader (Mobile Shader 也可以用在除移动平台之外的地方)
- 如果使用的是 Standard Shader,可以减少使用贴图的数量,这样 Unity 在编译时会自动优化。
- 需要分析所使用的 Image Effect 对整体 Fill Rate 的影响,如果实在影响很大,那就要禁用某些 Image Effect。
GPU Memory Bandwidth
- 在Profiler中查看GPU在渲染上花费的时长
- 在Quality Settings中降低Texture Quality设置
- 再次运行查看GPU的渲染时长
- 如果性能提升,就说明贴图太大,显存不够用
具体优化方法:
- Texture Compression
- Mipmap 和 Mipmap Streaming
Texture Compression
- Texture Compression 优化:
-
- 例如: Build 构建完成后,这会在Editor Log里面生成一份构建日志、打开 Window > Console 窗口,在 Console 窗口右上角三个小点图标那里点击 Open Editor Log 菜单。在打开的文本中搜索:Used Assets and files from the Resources folder, sorted by uncompressed size
这搜索到的地方可以看到所有在项目中使用的,但是没有被压缩过的贴图。 -
- 对图片的compression属性进行更改有几种对应的压缩方法
Mipmap 和 Mipmap Streaming
- Unity中的Mipmap相当于Texture的Lod技术,为了防止物体离相机较远时因为贴图的采样率不足导致出现条纹。Unity中生成Lod的方法很简单,在Texture的面板中勾选Generate Mip Maps,然后点Apply即可。生成完毕后可在下方查看每一级level所对应的图片,原理应该是均值采样法。
- mipmap是很典型的用存储空间占用换内存占用的方法(如果资源的 Z 方向上未发生变化,那么使用 Mipmap 是一种浪费:)
- Texture Steaming技术是解决Shader渲染时整个Texture Mipmap金字塔全部加载的问题,在模型离相机较远时的优化效果很好,换句话说大世界游戏的优化效果很不错.当 相机看到图片会计算得到一个合理的mipmap level,然后将其他level的mipmap全部裁剪。不进行加载,同样在MaxLevelReduction进行设置。在游戏运行时会得到一个先模糊再清晰的过程。
Vertex Processing
- 尽量减少网格上的顶点数量(简化模型)
- 使用 Normal Map 表现模型细节
- 如果模型上没有使用 Normal Map,可以在 Model Import Setting 界面禁用此模型的 Vertex Tangents 选项。
- 使用 LOD
- 降低自定义 Shader 中 Vertex shader 的复杂度(Vertex shader 是 shader 中告诉GPU如何画出每个顶点的代码块)
- 如果使用的是标准着色器,那么尽量使用简单的着色器,比如 Mobile Shader。
- 如果模型上没有使用 Normal Map,可以在 Model Import Setting 界面中将 Normals 设置为 None
- 如果不是要动态生成网格,或者需要在运行时拷贝网格的数据,Read/Write Enabled这个选项不应该打开。
- 如果打开读写,那么网格信息会被同时上传到 GPU 和 CPU;否则只会上传到 GPU。所以通常是不打开可读写选项,可以节省内存。
- 可以通过使用纹理和法线贴图来降低模型顶点数,合理布局UV避免接缝并让纹理可以均匀地分布在模型表面,复用模型和使用 Instancing 与 Static Batching 来进行模型合并减少Draw Call数量。
- 玩家会拿起物体靠的很近地观察,所以需要VR布局很均匀,因为在VR中可以从任何角度进行观察。
- 也要注意贴图接缝这些细节,因为玩家会仔细观察。
- 限制所用材质的数量可以减少 Draw Call,减轻GPU的负担。
- 使用模块化方式设计制作的模型可以尽量复用。
资产大小缩放建议:
- 确保场景中的物体有一致的尺寸大小非常重要。如果场景中的物体大小不统一,比如一扇房门比人还矮,或者一辆汽车像山一样大,都会让人觉得不真实。
- 另外,在普通游戏中常用的动态改变FOV的方法在VR中并不适用。在VR中,建议保持FOV不变。
- 在缩放场景中模型的时候,为了确保物理效果统一,也要相应的缩放碰撞体的大小。否则物理模拟的效果就会不真实。
- 人们在体验VR的时候,通常会把头钻进场景中的模型中,想要看到他们不该看到的地方。我们可以设计成当侦测到玩家把头转进模型中时,用渐变的方式把整个屏幕变黑。要做到这一点,我们要为所有相关模型设置正确的碰撞体大小才行。
|