Unity移动端性能优化
2017年05月04日 15:48
1 点赞
0 评论
更新于 2025-11-21 21:24
1. 渲染
1.1 渲染技术替代与优化
- 反射与折射处理:利用反射探针(reflect probe)代替反射、折射效果,尽量避免使用实时纹理(RTT)、GrabPass、RenderWithShader、CommandBuffer.Blit (BuiltinRenderTextureType.CurrentActive...) 等技术,因为这些技术可能会带来较高的性能开销。
- 后处理框架优化:建立统一的后处理框架,涵盖如泛光(bloom)、高动态范围(hdr)、景深(DOF)等效果。通过统一框架可以共用模糊函数,减少多次 blit 操作。同时,要特别注意 RTT 的尺寸设置,避免不必要的资源浪费。
- 特殊效果优化:对于空气折射、热浪扭曲等效果,由于 GrabPass 并非所有硬件都支持,建议改为使用 RTT 或者后处理来实现,以提高兼容性和性能。
1.2 Shader 优化
- Shader 材质统一:建立统一的 shader 材质来代替单一 shader。充分利用 shader_feature、multi_compile 等特性,并将宏开关显示于界面,方便调试和管理。
- 纹理混合与 Pass 剔除:采用图像混合代替多通道纹理,对于不需要的 pass,如阴影投射、阴影接收、MetaPass、forwardadd 等,要及时剔除,减少渲染负担。
- Alpha 相关操作优化:尽量少用 alpha test、discard、clip、Alpha Converage 等操作,因为这些操作会影响 Early - Z Culling、HSR 的优化效果。
- Alpha 穿透问题避免:避免出现 Alpha Blend 穿透问题,同时要注意权重混合、深度剥离等透明排序方法代价较大,需谨慎使用。
1.3 光照与阴影优化
- 光照方式选择:使用光照贴图代替动态阴影,尽量减少实时光的使用。对于阴影贴图和环境贴图,可使用 16 位代替 32 位,以降低内存占用。也可以利用 projector + rtt 或者光圈代替实时阴影。
- Shader 全局参数管理:将环境参数(如风、雨、太阳等)等 shader 全局参数进行统一管理,提高代码的可维护性。
1.4 渲染材质选择
- 物理渲染优化:对于非主角对象,可以使用 matcap 代替基于物理的渲染(pbr)。对于无金属材质,不一定要使用 pbr。仔细选择物理渲染所用的 FDG 参数(F: schlick、cook - torrance、lerp,要求不高时用 4 次方;D:blinn - phong、beckmann、GGX、GGX Anisotropic;G: neumann、cook - torrance、Kelemen、SmithGGX;standard shader 要注意选择 BRDF1 - BRDF3)。在渲染要求不高时,可不用 GGX,也可以用 LH 来优化 GGX。
- Shader 数据类型优化:使用 fixed、half 代替 float,建立 shader 统一类型。其中,fixed 效率是 float 的 4 倍,half 是 float 的 2 倍。同时,要小心选择 shader 变量的修饰符(uniform、static、全局),优先选择 Mobile 或 Unlit 目录下的 shader。
1.5 其他渲染优化
- 高低配渲染与 Mipmap:使用高低配渲染策略,在内存足够时可以考虑开启 mipmap,以提高不同设备上的渲染性能。
- Surface Shader 功能关闭:使用 surface shader 时,注意关掉不用的功能,如 noshadow、noambient、novertexlights、nolightmap、nodynlightmap、nodirlightmap、nofog、nometa、noforwardadd 等。
- Standard Shader 优化:standard shader 的变体过多(3 万多),会导致编译时间较长,内存占用也很惊人(接近 1G)。如果使用 standard shader,要关掉没用的 shader_feature,如 _PARALLAXMAP、SHADOWS_SOFT、DIRLIGHTMAP_COMBINED DIRLIGHTMAP_SEPARATE、_DETAIL_MULX2、_ALPHAPREMULTIPLY_ON 等,同时去掉多余的 pass。
- Shader 生成工具代码优化:shaderforge、Amplify Shader Editor 生成的 shader 可能存在多余代码,需要程序专门进行优化。Amplify Shader Editor 功能更强大且开源,建议学习使用。
- 地形处理:不要使用 unity 自带的 terrain,因为即使只用 3 张 splat 图,shader 也是对应 4 个的。建议使用 T4M 或者将地形转为 mesh。
- 实例化优化:当模型和材质相同且数量巨大时,如草等对象,可以使用 Instance 来优化渲染性能。
- 查找纹理优化:利用查找纹理(LUT)来优化复杂的光照渲染,例如皮肤、头发、喷漆等效果。
- 特殊效果避免:尽量不要使用 Procedural Sky,因为计算瑞丽散射和米氏散射的效率比较低。同时,尽量不要使用 speedtree,可改为模型加简单树叶动画。不过,SpeedTreeWind.cginc 里面的动画函数很丰富,TerrianEngine 中的 SmoothTriangleWave 很好用。
- Shader 性能调试:多用调试工具检查 shader 性能,常用工具有 FrameDebug、Nsight、RenderDoc 、AMD GPU ShaderAnalyzer / PVRShaderEditor、Adreno Profiler 、腾讯 Cube、UWA 等。另外,可以内置 GM 界面,如开关阴影、批量替换 shader 等,方便真机调试。
2. 脚本
2.1 查找与调用优化
- 减少查找函数调用:减少 GetComponent、find 等查找函数在 Update 等循环函数中的调用。可以使用 go.CompareTag 代替 go.tag,以提高性能。
- 减少同步函数调用:减少 SendMessage 等同步函数的调用,同时减少字符串连接操作。建议使用 for 循环代替 foreach 循环,虽然 5.5 以后版本的 foreach 已经优化过,但 for 循环在性能上仍然更优。此外,要少用 linq。
2.2 资源加载与协程处理
- 异步加载大资源:将大资源改为异步加载,避免阻塞主线程。
- 合理处理协程调用:合理安排协程的调用,避免协程过多导致性能问题。
2.3 线程管理与发布优化
- 单独线程处理:将 AI、网络等操作放在单独线程中进行,以提高主线程的性能。
- 发布优化:发布时关闭 log,剔除不必要的代码,减少资源占用。
2.4 随机数与脚本挂载优化
- 伪随机数使用:使用伪随机数,提高随机数生成的效率。
- 脚本挂载类优化:将脚本挂载类改为 Manager 等全局类实现,方便管理和维护。
2.5 Lua 脚本优化
- Lua 循环函数避免:在 lua 中尽量不实现 update、fixedupdate 等循环函数,因为 lua 和 csharp 互调用的效率比较低。
3. 内存管理
3.1 资源池管理
- 小资源池化:使用池子管理粒子、float UI 等小资源,因为频繁地垃圾回收(GC)会造成卡顿。
- 主动调用 GC:必要时主动调用 GC.Collect(),但要注意调用的时机,避免过度调用影响性能。
3.2 资源生命周期管理
- 统一接口与引用计数:按照不同资源、不同设备管理资源生命周期,将 Resources.Load 和 Assetbundle 统一接口,利用引用计数来管理生命周期,并打印和观察生命周期。确保资源随场景而卸载,不常驻内存,明确哪些是预加载资源,哪些可能存在泄漏。
3.3 内存泄漏检测
- 检测方法:对于 Container 内资源,如果不 remove 掉,使用 Resources.UnloadUnusedAssets 是卸载不掉的。建议直接通过 Profiler Memory 中的 Take Sample 来对其进行检测,通过直接查看 WebStream 或 SerializedFile 中的 AssetBundle 名称,即可判断是否存在“泄露”情况。也可以通过 Android PSS/iOS Instrument 反馈的 App 线程内存来查看。
3.4 堆内存与 CPU 占用优化
- 避免大堆内存分配:避免一次性进行过大的堆内存分配,因为 Mono 的堆内存一旦分配,就不会返还给系统,会导致堆内存只升不降。常见的导致大堆内存分配的情况有高频调用 new、大量 log 输出等。
- CPU 占用优化:NGui 的重建网格会导致 UIPanel.LateUpdate 开销较大,可以按照静止、移动、高频移动来切分 UIPanel。同时,NGUI 锚点自身的更新逻辑也会消耗不少 CPU 开销,即使控件静止不动,锚点也会每帧更新。可以修改 NGUI 的内部代码,使锚点只在必要时更新,一般只在控件初始化和屏幕大小发生变化时更新即可。不过,这个优化的代价是控件的顶点位置发生变化时,上层逻辑需要自己负责更新锚点。另外,加载资源时使用协程,控制同一个 UIPanel 中动态 UI 元素的数量,建议将运动血条等动态 UI 分离成不同的 UIPanel,每组 UIPanel 下 5 - 10 个动态 UI 为宜,以降低单帧中 UIPanel 的重建开销。
3.5 资源冗余处理
- 避免资源重复打包:避免 AssetBundle 打包到多份中,同时要注意动态修改资源导致的 Instance 拷贝多份的问题,如动态修改材质(Renderer.meterial)、Animation.AddClip 等。
3.6 磁盘换内存策略
- 本地缓存使用:对于占用 WebStream 较大的 AssetBundle 文件(如 UI Atlas 相关的 AssetBundle 文件等),建议使用 LoadFromCacheOrDownLoad 或 CreateFromFile 来进行替换,即将解压后的 AssetBundle 数据存储于本地 Cache 中进行使用。这种做法适合内存特别吃紧的项目,通过本地的磁盘空间来换取内存空间。
4. 美术
4.1 资源审查规范与工具
- 规范建立:建立资源审查规范和审查工具,包括 PBR 材质贴图制作规范、场景制作资源控制规范、角色制作规范、特效制作规范等。可以利用 AssetPostprocessor 建立审查工具,提高资源质量和一致性。
4.2 资源压缩与优化
- 多类型资源压缩:对纹理、精灵填充率、动画、声音、UI 等资源进行压缩。例如,九宫格 UI 优于拉伸 UI。同时,要严格控制模型面数、纹理数、角色骨骼数。
- 粒子效果优化:对于粒子效果,可以录制动画代替粒子,减少粒子数量,避免粒子碰撞,以降低性能开销。
- 角色优化:启用 Optimize Game Objects 减少角色节点,使用(SimpleLOD、Cruncher)优化角色面数。
- 模型导入检查:导入模型时,检查 Read/Write only、Optimize Mesh、法线切线、color 等参数,禁用 Mipmap 以减少内存占用。
- 纹理压缩问题处理:注意压缩纹理可能导致色阶不足的问题。对于无透明通道的纹理,建议使用 ETC1 格式。目前安卓不支持 ETC2 的设备已不足 5%,建议放弃分离通道的办法。
4.3 UI 优化
- UI 元素分离:尽可能将动态 UI 元素和静态 UI 元素分离到不同的 UIPanel 中,因为 UI 的重建以 UIPanel 为单位,这样可以将因为变动的 UI 元素引起的重构控制在较小的范围内。同时,尽可能让运动频率不同的 UI 元素分离放在不同的 UIPanel 中。
- ugui 优化:对于 ugui,可以充分利用 canvas 来切分不同元素,提高 UI 渲染效率。
- 大贴图处理:大贴图会导致卡顿,可以将其切分为多个小贴图进行加载。
- 音频压缩:iOS 使用 mp3 压缩音频,Android 使用 Vorbis 压缩音频。
5. 批次
5.1 批次开启
- 静态批次:开启 static batch,提高静态物体的渲染效率。
- 动态批次:开启 dynamic batch,但要注意其使用条件,要求模型小于 900 顶点,用法线小于 300,用切线小于 180。缩放不一致、使用 lightmap、多通道材质等情况会使 dynamic batch 无效。
5.2 物体数量与批次平衡
- 减少 GameObject:减少场景中的 GameObject 数量,因为场景模型数量对 fps 影响巨大。
- 批次与总线压力:批次并非越少越好,过大的渲染数据会给总线传输带来压力,需要在批次数量和渲染数据量之间找到平衡。
6. 物理
6.1 物体属性设置
- 静态物体设置:将不需要移动的物体设为 Static,以减少物理模拟的开销。
- 碰撞体优化:不要使用 Mesh 碰撞,对于角色可以不使用碰撞体,以降低物理计算的复杂度。
6.2 触发器与逻辑频率优化
- 触发器逻辑优化:对触发器逻辑进行优化,减少不必要的计算。
- 频率调整:调整寻路频率、AI 逻辑频率 、Fixed Timestep,可将帧率降低到 30,以提高性能。
6.3 复杂计算处理
- 异步处理复杂计算:对于出现卡顿的复杂计算,如寻路、大量资源加载等,可以使用分帧或者协程异步来处理,避免阻塞主线程。