Unity 性能优化

Unity 性能优化
NyxXUnity性能优化
性能优化问题的本质 - 慢与快的问题
- 前提
- 稳定性:不能因优化造成稳定性变差
- 兼容性:不能因优化导致兼容性变差
- 性价比:优化要有度,考虑成本与复杂度
- 性能优化的流程
- 发现问题(什么平台、什么操作系统、什么情况下出现问题,一般问题还是特例问题等)
- 定位问题(什么地方造成的性能问题,我们要用什么工具、什么方法确定瓶颈)
- 研究问题(确定用什么方案处理这个问题,要考虑性能优化的前提)
- 解决问题(按问题研究的结论去实际处理,并验证处理结果与预期的一致性)
- 影响性能的四大类问题
- CPU、GPU、内存、带宽
- 性能问题可能的情况(可能性按由高到低的顺序排列)
- CPU利用率
- 带宽利用率
- CPU/GPU强制同步
- 片元着色器指令
- 几何图形到CPU到GPU的传输
- 纹理CPU到GPU的传输
- 顶点着色器指令
- 几何图形复杂性
资源优化
- 模型:
- 降低模型面数、低模Mesh+高模法线、凹凸纹理
- 对于不会动态修改的网格关闭Read/Write,这样Unity只会将Mesh上传到GPU、不会在CPU内存中也保存一份。关闭会导致MeshCollider变得不可用
- 勾选Optimize Mesh、Mesh Compression
- 使用LOD,降低渲染压力,但会增加包体大小
- 降低模型面数、低模Mesh+高模法线、凹凸纹理
- 纹理资源
- 降低分辨率,最好不要超过2048*2048
- 压缩算法,尽量将纹理大小设置为2的幂次方,来兼容更多压缩算法
- 取消勾选Read/Write Enabled
- 禁用多于 MipMap,可以减少1/3体积,针对距离不会改变的贴图
- 使用图集,相同纹理图集的静态网格可以进行静态和批,减少 DrawCall 数量
- 图集划分的粒度
- 共用的common不能太大。
- 一个功能一个图集 登录,背包,技能,角色,商店等。
- 一同出现的最好一个图集,比如主界面一个图集。
- 图集划分的粒度
- 动画资源
- 2D帧动画转为spine动画
- 3D动画可以复用人体动画
- 降低帧率
- 脚本资源
- 减少第三方插件
- 考虑代码复用
- 音频
- 启动Force To Mono选项,强制单声道,减少音频文件大小
- 压缩, Vorbis 格式
- 减少采样率,通常22050Hz
- 字体
- 剔除字库中用不到的字
- 配置数据
- 将文本格式转换为二进制格式,如 protbuf
- AB包
- 压缩算法
- 远程部署、动态下载
场景优化
- 合理设计场景一级节点的同时,避免场景节点深度太深,一些代码生成的游戏对象如果不需要随父节点进行Transform的,一律放到根节点下。
- 尽量使用Prefab节点构建场景,而不是直接创建的GameObject节点。
- 避免DontDestroyOnLoad节点下太多生命周期太长或引用资源过多的复杂节点对象。Additive场景尤其要注意。
- 最好为一些需要经常访问的节点添加tag,可以极大加快查找速率
- 静态节点一定要添加Static标记,减少不必要的计算
UGUI优化
减少DrawCall角度(尽可能合批):
- 使用图集:防止纹理切换打断合批,减少DrawCall数量。
- 避免使用Mask:会生成额外的DrawCall ,可以用RectMask替代
- 避免ABA的问题:图集B的图片夹在两个来自图集A的图片之间,会增加DrawCall
减少UI加载带来CPU消耗的角度
CPU主要两点消耗:1.动态和批、2.加载UI、3.GraphicRaycaster与所有UI元素求交
使用对象池:本质是内存换CPU时间,频繁创建和销毁UI元素会增加CPU开销,可以使用对象池进行缓存。
UI预加载:开始游戏预先加载UI资源但不实例化,使用UIManager缓存起来,或者实例化后隐藏起来,打开后显示。
- 拆分大的UI:将UI中默认隐藏的界面拆分出来,减少网格重建的复杂度。
动静分离:将动和静的UI元素放到不同Canvas下,减少合并网格带来的CPU开销。
异步加载:协程+回调
合批过程(rebatch)(非主线程运行)
- 根据UI元素深度关系进行排序
- 检查UI元素的覆盖关系
- 检查UI元素材质并进行合批
渲染细节
- UGUI中渲染是在Transparent
半透明渲染队列中完成的,半透明队列的绘制顺序是从后往前画,由于UI元素做Alpha Blend,我们在做UI时很难保障每一个像素不被重画,UI的Overdraw太高,这会造成片元着色器利用率过高,造成GPU负担。 - UI SpriteAtlas图集利用率不高的情况下,大量完全透明的像素被采样也会导致像素被重绘,再成片元着色器利用率过高;同时纹理采样器浪费了大量采样在无效的像素上,导致需要采样的图集像素不能尽快的被采样,造成纹理采样器的填充率过低同样也会带来性能问题。
- UGUI中渲染是在Transparent
Re-Build 网格重建过程(在ReBatch过程中完成)
CanvasUpdateRegistry中维护了两个队列m_LayoutRebuildQueue和m_GraphicRebuildQueue,每个继承Graphic的对象在顶点、材质、布局等信息发送改变后会调用setDirty方法向这某个队列中添加自身。Canvs中的willRenderCanvases事件回调函数会执行CanvasUpdateRegistry的PerformUpdatef方法,方法中会调用队列中没有被销毁的ICanvasElement的Rebuild方法,方法内部会重新计算网格、材质、纹理数据,并调用canvasRenderer.SetMesh、canvasRenderer.SetMaterial、canvasRenderer.SetTexture方法
Canvas使用准则(优化)
- 将所有可能打断合批的层移到最下边的图层,尽量避免UI元素出现重叠区域
- 可以拆分使用多个同级或嵌套的Canvas来减少Canvas的Rebatch复杂度
- 拆分动态和静态对象放到不同Canvas下。需要取舍,动静分离会打断合批,增加DrawCall,而在UI元素少的情况下DrawCall消耗可能会大于合批。所以在UI元素过多的情况下考虑动静分离。
- 将文字和图片放在不同的Canvas下,以防打断合批(或者考虑将文字直接放在图片中)
- 不使用Layout组件
射线(Raycaster)优化
- 必要的需要交互UI组件才开启“Raycast Target”
- 开启“Raycast Targets”的UI组件越少,层级越浅,性能越好
- 对于复杂的控件,尽量在根节点开启“Raycast Target”
- 对于嵌套的Canvas,OverrideSorting属性会打断射线,可以降低层级遍历的成本
Batching
- 资源Batcing
- Mesh
- Mesh.CombineMesh,合并静态网格对象
- SetTexture
- AtlasTexture
- TextureArray
- Shader变量与材质属性
- Material Property Block(Build In管线)
- Const buffer(SRP管线)
- Mesh
- Draw Call Batching
- Static Batching
- 合并静态网格
- 存在内存开销
- 64000个顶点限制,超过会创建一个新的合批
- 影响Culling剔除
- Dynamic Batching
- CPU开销大
- 900个顶点属性,(位置、法线、切线、uv都算顶点属性)
- 材质实例不同也不能合批
- 有多个shader pass的游戏对象不能合批
- 延迟渲染不支持动态和批
- Static Batching
- GPU Instancing
- 用于一次渲染一份网格的多个实例
- 不与SRP Batching兼容
- 有图形API版本要求
- 渲染顶点数较少的网格效率会比较差
- Set Pas Call Batching
- SRP Batching
- 适用使用相同Shader的物体
- 有图形API版本要求,必须支持const buffer
- 必须是SRP渲染管线
- SRP Batching
- 合批失败原因汇总
- 此物体受到多个前向灯光的影响
- 此物体有不同的材质
- 此物体使用了多pass着色器
- 此物体Trasform的Scale使用了负数
- 此物体接收阴影的设置不同,或者物体有不同的的阴影距离设置
- …
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果




