unity移动端性能优化技术整理(持续更新)

目录

  • Overview
    • 性能优化主要针对四大类问题
      • 填充率
      • 发热量
      • 功耗比
    • 瓶颈可能性的顺序
  • 性能分析工具
  • 性能测试方法
    • 确定瓶颈
    • CPU瓶颈
    • GPU瓶颈
      • 确定顶点瓶颈
      • 确定片元瓶颈
      • 确定带宽瓶颈
    • 增加开关
    • 打包真机测试
  • CPU瓶颈优化
    • 优化draw call
    • set pass call batching (SRP Batcher)
      • SRP Batcher失败原因收集:
    • Resource batching(网格、贴图、shader参数、材质属性)
      • dynamic batch
      • static batching
      • gpu instancing
    • 物理
      • 工程设置
    • 寻路优化
    • 优化动画计算开销
    • 逻辑、脚本
    • UI
  • 内存优化
    • 减少变体
    • 贴图
    • 关闭static batching
    • 动画
    • Buffer
    • 音频
    • 网格
  • GPU瓶颈优化
    • LOD场景模型、shader
    • Imposter(插件名,主要代指这种优化方式)
    • 通过烘焙光照简化实时光照
    • 贴图
    • 使用OnDemand更新或分级设置接口
    • 骨骼 animation lod
    • 剔除
      • back face cull
      • early z & pre Z pass
      • Frustum cull
      • Occlusion Culling
      • 空间加速结构
      • GPU culling
      • 软光栅
    • Overdraw
      • 修改渲染架构
      • 特效、粒子
      • 场景
    • 带宽
    • 优化渲染顺序
    • shader
    • UI
    • 渲染特性优化
      • 平面反射
      • 抗锯齿
      • 后期效果
        • Color grading
        • DOF 景深
        • Motion blur
        • Vigette
        • Lens flare
        • bloom
      • render feature
      • 体积渲染
  • 参考

Overview

本文主要针对一些常见的性能优化的点进行总结。属于一个high level的overview。需要注意只有当这些模块确定成为制约帧时间的瓶颈时,才能够进行针对性地优化,否则可能会适得其反,事倍功半。
一般来说,移动端性能优化,如果同时支持ios和android,建议先从ios开始优化,因为ios的工具链更加强大一些。先做共性的性能优化,在做兼容性方面的性能优化。

性能优化主要针对四大类问题

  • CPU
  • GPU
  • 带宽
  • 内存
    除此之外,还有一些隐藏的问题。一般不太容易进行针对性优化。

填充率

有时候一个性能消耗巨大的步骤会对前后的步骤造成阻塞影响。举个例子时,一辆卡车只能装载30吨的货物,如果货物一共有50吨时就需要运载2次,当优化到30吨时会带来巨大的提升。如果一次运输超过了带宽的大小,那么就要分2次运输,如果一次渲染的填充率不足以填满render target,那么就需要填充两次。所以降采样有时候能够带来巨大的收益,不局限于其本身的耗时。

发热量

发热过大会导致SOC温度过高,最后降频,降低性能。

功耗比

SOC运行在一个较高的频率会增加发热和功耗,发热多了就降频。功耗高了掉电就快,因此最好能够将CPU的负载控制在一个合理的区间,并不只是在帧时间上进行优化。

瓶颈可能性的顺序

  • CPU利用率
  • 带宽利用率
  • GPU/GPU强制同步
  • 片元着色器
  • 几何图形从CPU到GPU的传输
  • 纹理CPU到GPU的传输
  • 顶点着色器指令
  • 几何图形的复杂性

性能分析工具

  • unity内置profiler(包括memory tree、全平台)
  • Frame debugger(全平台)
  • Snap dragon profiler(高通SOC)
  • RenderDoc(PC、安卓)
  • IntelGPA(PC)
  • XCode(Apple平台)
  • NSight(PC)
  • mail graphics debugger(mail系列GPU)
  • UWA
  • UPR
    工具不是万能的,有时候也需要个人经验的判断来确定瓶颈。

性能测试方法

性能测试定位瓶颈的方法一般都是各种各样的控制变量。很多时候基本都是通过排列组合,一遍遍试才能真正确定的。特别注意优化需要建立在问题已经被定位到的基础上。

确定瓶颈

通过profiler能够一定程度上来确定瓶颈在哪,当然不能单一地只看profiler,需要结合其他分析工具,因为profile有时候非常不准。需要注意的是profiler最好运用在目标平台上,例如adb连接安卓设备,mac上连接苹果设备等。

CPU瓶颈

profiler和集成的uwa可大致判断函数的消耗,在PC上确定移动端的瓶颈可以通过限制PC上的核心频率和线程数来模拟。

GPU瓶颈

profiler和xcode可大致判断,观察一帧时间分布,例如CPU多少毫秒,GPU花了多少毫秒,unity profiler中cpu是否有wait for gfx之类的函数。

确定顶点瓶颈

尝试加减几何体或增减顶点数。

确定片元瓶颈

增减分辨率

确定带宽瓶颈

采样一个虚假的极小的贴图可以屏蔽带宽的因素等等

增加开关

无法衡量的功能消耗的情况:我们可以通过控制变量计算出大致的消耗,比如打不同版本的包横向对比、在屏幕上使用按钮做功能的开关、使用lua配置等方法开关。

打包真机测试

开关特性、设置打包对比

CPU瓶颈优化

如果确定是CPU的瓶颈,以下几个模块是当这些功能成为性能开销较大的模块时的优化方向

优化draw call

draw call是造成cpu瓶颈的因素之一,如果draw call过多,cpu会消耗大量的时候在应用阶段准备渲染所需的数据以及设置渲染的状态等。因此可以通过以下几个合批手段来进行优化

set pass call batching (SRP Batcher)

srp batcher主要用来降低set pass call。使用时需要注意其限制
限制:

  • 图形API版本要求
  • 必须是SRP渲染管线
  • 粒子对象不能合批
  • 使用material property blocks的游戏对象不能合批
  • shader必须是compatible的
    Cbuffer的类型:
  • unityPerCamera
  • unityPerFrame
  • unityPerPass
  • unityPerDraw
  • unityPerDrawRate
  • unityPerMaterial

SRP Batcher失败原因收集:

暂略。

Resource batching(网格、贴图、shader参数、材质属性)

将需要传输的数据一次性传输,节约CPU向GPU传递渲染所需资源的次数。

dynamic batch

  • 900个顶点属性限制
  • 除了渲染阴影对象外,相同材质,不同材质实例也不能合并
  • 有多shader pass的游戏对象无法合批
  • 受多个光照影响的游戏对象,满足动态合批条件合批后,只会受一个光源的影响
  • 不支持延迟渲染
  • CPU开销可能会增大,需要测试开启使用

static batching

  • 额外内存开销
  • 64000个顶点限制
  • 影响culling剔除

gpu instancing

在SRP batcher中不兼容,需要手动写代码调用如下三个Api:

  • draw mesh instanced
  • draw mesh instanced indirect
  • Draw mesh instanced procedual
    限制:
  • 图形API版本要求
  • 不同绘制API的参数与绘制个数不同。
  • 渲染顶点数较少的网格时,效率可能会更差。

物理

  • 使用简单的碰撞体例如球和方块等简化场景的碰撞检测。
  • 用ray cast代替sphere cast、capsuleCast等。
  • 如无需要尽量使用trigger,降低CPU开销。
  • 量少用mesh collider
  • 尽量减少rigid body的数量
  • ray cast和overlay尽量使用noalloc的函数版本,降低gc开销
  • 如果是大量的ray cast的操作还可以通过ray cast command的方式批量处理,充分利用job system来分摊到多核多线程计算。

工程设置

  • Auto sync transforms: transform组件发生变化时强制进行物理更新,会增加物理运算负担,一般不开启
  • Reuse collision callback: 尽量保持开启,会重用之前的碰撞结果实例,降低GC开销
  • default solver iteration:迭代次数越高,物理越精确,CPU开销越大
  • broad-phase type:选择合理的粗筛方法。
  • 选择合适的time进行物理更新间隔
  • 勾选player settings选项中的的prebake collision meshes来在构建引用时预先bake出碰撞网络
  • 设置合理的碰撞层级

寻路优化

  • 2d寻路代替navigation mesh

优化动画计算开销

  • 减少粒子系统、动画系统的使用,使用脚本或轻量级的动画系统例如dotween来代替。
  • 尽量少的骨骼数量,可在project settings中进行控制(引起CPU和GPU双方的性能瓶颈,主要是更新骨骼的顶点)
  • 优化skin weights,默认4根骨骼,但对于一些不重要的动画对象可以减少到一根,节约计算量
  • 打开动画文件的optimzie game object选项,这个选项使用unity默认动画内部结构骨骼,消减骨骼transform带来的性能开销。但有时候会造成动画错误,需要留心测试。
  • 打开动画文件的optimize bones,自动剔除没有蒙皮顶点的骨骼。
  • 如果是不需要动画的模型,导入时直接关闭动画。(animation type选none)

逻辑、脚本

  • profile定位耗时长的函数,针对性优化。
  • 尽量在start中做一些重消耗的api调用

UI

UGUI中的UI造成的CPU开销主要来自于canvas re-batch时间过长。每一个canvas在绘制之前都有进行合批。如果UI不变则不需要重新合批。re-batch一般在线程中进行,移动端核心数的不同影响实际性能。造成re-batch的主要有以下几种情况:

  • canvas中的UI顶数据过多,生成时间过长
  • 将所有可能打断合批的层移到最下边的图层。尽量避免UI元素出现重叠区域。
  • 可以拆分使用多个同级或嵌套的canvas来减少canvas的rebatch复杂度
  • 拆分动态和静态对象放置到不同的canvas中
  • 不适用layout组件
  • canvas的render mode尽量overlay模式,减少camera调用的开销
  • 必要的需要交互的组件才开启raycast target
  • 开启raycast target的UI组件越少,层级越浅,对性能越好。
  • 对于复杂的控件,尽量在根节点开启raycast target。
  • 避免字体框重叠,打断合批。
  • 避免字体网格重建,一般发生在UIText组件发生变化时;父级对象发生变化时;UI组件或其父对象enable/disable时。
  • 不要使用透明度来控制UI显示/隐藏
  • scroll view选择rect mask 2d组件裁剪。使用对象池作为实例化缓存。

内存优化

以下主要针对内存瓶颈的优化。

减少变体

一个shader的变体太多会加载太多这个shader的版本造成大量的内存开销。

贴图

  • 选择合适的贴图分辨率,比如根据游戏对象占据当前镜头屏幕百分比的大小来决定贴图的分辨率
  • 选择合适的贴图加载策略,如virutal texture, mipmap streaming等。
  • 不要通过增大贴图的方式增加细节。可以引入detail mask。
  • 根据平台的中高低配准备多套资源级别。
  • 根据平台选择合理的纹理压缩算法。
  • 使用纹理图集
  • 选择合适的纹理过滤方法。不需要mipmap的不打开mipmap,尽量使用mipmap streaming。各向异性过滤如无必要尽量不打开。
  • 确认贴图中的alpha通道是否要使用。不使用直接关闭。
  • 提高图集的利用率
  • 序列帧动画打图集,减少过多的序列帧。
  • 使用通道复用技术减少纹理的浪费
  • 使用UI九宫格来减少重复贴图
  • V坐标无变化的渐变图使用单像素宽度

关闭static batching

根据场景有时static batching会造成额外的内存开销。需要注意甄别。

动画

  • 使用动画压缩,在不影响效果的前提下尽量使用更激进的策略。

Buffer

  • 减少效果RT的分辨率

音频

  • 根据平台降低采样率
  • 根据音频选择合适的加载方式,如背景音乐等大的音频,使用流式加载策略。
  • 根据平台选择合适的压缩方式
  • 当游戏静音时,不要简单的关闭音频的game object,而是要销毁audio source的音频组件(待验证)

网格

  • 尽量少的材质个数,多材质id网格,Unity会创建多个sub mesh(待验证)
  • 尽量少的骨骼数量(引起CPU和GPU双方的性能瓶颈,主要是更新骨骼的顶点)
  • 在不影响效果的前提下尽量使用更激进的压缩方式(mesh compression)
  • read/write 一般默认关闭,否则会在运行时多拷贝一份,只有在运行时需要修改网格时才打开,或者是skin mesh
  • index format 如果定点数量不超过65535,可以使用16位索引
  • generate light map uv 不烘焙可以关闭
  • 启用project settings中的optimize mesh data,这个选项会删除网格中不需要的顶点属性,如切线法线UV等。

GPU瓶颈优化

GPU瓶颈是一个技术含量较高的领域,要做好这里的优化需要专业的引擎、图程或TA人员。因为优化渲染性能的同时还要保证效果和兼容性绝对不是一个简单的任务。

LOD场景模型、shader

Lod shader通常比lod 模型更重要,因为中高端机的顶点瓶颈的可能性很小。

  • 使用unity的组件lod group来根据视距设置lod级别的prefab。
  • 实现动态lod如UE4中的功能。(需要shader model 4.5支持曲面细分等特性?)

Imposter(插件名,主要代指这种优化方式)

对于移动较少的场景,可以通过渲染到图片的假背景进行优化,例如暂停界面的模糊场景背景等,人物展示界面静止的背景等。

通过烘焙光照简化实时光照

  • 例如shadowmask烘焙过的静态阴影,实时投影的pass中就不会再包含他了。
  • 如果光照较为简单,可以直接用烘焙的方式决定大部分的颜色,减少实时计算的开销。

贴图

  • 使用纹理图集帮助合批
  • 选择合适的纹理过滤方法。三线性和各向异性均会带来额外的采样次数。
  • 使用2d array纹理数组,可以提高访问效率。

使用OnDemand更新或分级设置接口

比如在特定场合做低于设计帧数的更新,例如UI弹出时等情况。

骨骼 animation lod

  • 尽量少的skin mesh renderer

剔除

back face cull

尽量不使用双面渲染

early z & pre Z pass

提前深度测试来剔除不必要的片元开销。

Frustum cull

根据摄像机的视椎体来对物体进行裁剪,unity默认有

Occlusion Culling

烘焙遮挡剔除数据。带来内存开销。

空间加速结构

Octree, BSP Tree, Portal, Voxelization

GPU culling

Hi-Z, temporal reprojection culling, cluster, tiled-based visibility buffer

软光栅

Overdraw

修改渲染架构

使用TBDR CBDR forward+等架构剔除冗余光源计算,推迟光照计算到管线的最后一步

特效、粒子

  • 优化特效粒子使用的贴图,如果是透明度贴图,尽量使用裁剪面片的方式实现。

场景

尽量减少透贴效果的使用。

带宽

带宽,在移动端,bandwith是一个多种设备(cpu gpu audio等)共享的资源,而且处理器通过带宽对存储的访问是一个很耗电的操作,所以,尽可能的提高在gpu内部cache上访问的到的可能性,减少对内存访问。无论是对代码的cache,贴图的压缩,mimapmap都是在做这种努力

  • 修改RT申请的方式,比如load store dont care来减少带宽的开销,提高缓存命中率。这部分可以通过metal frame debugger的渲染依赖图来帮助修改。
  • 贴图过滤方式尽量减少三线性和各向异性的使用,因为需要采样额外的mipmap。

优化渲染顺序

  • 在有early-z支持的移动端设备上打开depth prepass。
  • 在支持sub pass的机型上多利用sub pass来进行多pass的渲染效果绘制。

shader

shader中的每句语句的开销可以通过新版xcode来检查。

  • 使用step smoothstep lerp来代替分支语句,如果一定要用if可以配合flatten等指令
  • 使用define循环次数配合unloop来让编译器展开for循环。不超过平台指定的指令数上限。
  • 对于不重要的物体,在顶点着色器中计算光照,在光栅化阶段自动插值来计算颜色。
  • 使用compute shader来进行一些复杂的运算。
  • 顺序声明和使用变量,因为GPU是并行的,在一个thread中执行外的内容也许在另外一个thread也需要被使用到,因此如果此时该变量还在cache中,就能够提高cahce的命中率
  • 少使用函数或者跳转语句等
  • 将常用的数据变量和寄存器或cache对齐,从urp内置的shader的cbuffer声明可以看出unity官方也保持着这种做法。
  • 减少纹理的采样,例如一次逻辑控制的UV采样可以通过修改UV采样坐标来避免两次采样。

UI

  • 用纹理文字代替系统文字(视情况而定)
  • 减少UI的overdraw,减少完全透明像素的采样。
  • 全屏UI,关闭背景渲染

渲染特性优化

一般来说,渲染特性的优化主要以下几个手段:

  • 降采样
  • 低模
  • 简化shader计算
  • 减少pass
  • 减少频繁的load/store action
  • 预结算空间换时间等
  • 减少render target的切换开销,clear命令,如平台支持多使用sub pass等。
  • 使用尽量简单的RT格式,如避免64位RT的使用。
  • 降低depth buffer的精度,32位有更好的cache性能(待测试)
  • 使用移动端简化的光照模型,如ARM为移动端优化的PBR(URP中的PBR模型)

平面反射

使用单pass翻转矩阵平面反射,避免镜头方案的使用

  • 深度优化layer mask
  • 简化反射绘制的shader
  • 根据场景动态开关pass
  • 使用低模来绘制反射
    在合适的平台使用SSPR

抗锯齿

在前向渲染中,MSAA>FXAA>TAA>SMAA
在延迟渲染中,FXAA>TAA>>SMAA

后期效果

后期效果往往涉及全屏的blit,尤其复杂的效果涉及步进等计算,在移动端更是灾难。
一般而言不论是何种后期,最好都有以下的优化手段:

  • 使用脚本进行后期效果的动态开关。
  • 使用local volume进行所需区域的后效配置,减少全局常驻效果。
  • 尽量不使用多camera的方式来实现后期的分层。
  • 如果能够在材质上实现雾效,尽量在材质中实现(如果受到雾效影响的场景物体较少)
Color grading

color grading的dynamic range在支持浮点精度纹理的设备上使用high会更高效

DOF 景深

尽量使用gaussian模式

Motion blur
  • 质量分级
  • intensity强度
  • clamp摄像机旋转产生的速度可以具有的最大长度。
Vigette
  • Intensity和smoothness
Lens flare

occlusion设置与光晕数量。

bloom
  • 减少高斯迭代次数。
  • 使用更好的bloom算法,如参考中UE4的实现。
  • 替换2遍pass的模糊算法,如kwase blur和duel blur

render feature

  • 对不同作用的摄像机赋予不同的renderer,避免冗余的render feature的clear消耗。
  • 对不需要的render feature要及时的关闭。例如depth normal pass在不使用ssao等效果的情况下可以关闭。

体积渲染

  • 使用根据深度增大的步进
  • 使用抖动和降低的步进次数
  • 降低分辨率
  • 步进算法使用define和展开命令让编译器进行优化,绝对不能使用变量作为for循环的循环次数!
  • 使用基于时间的特性来降低单帧的渲染消耗。

参考

https://space.bilibili.com/1311706157/video
https://cdn2-unrealengine-1251447533.file.myqcloud.com/Resources/files/GDC2014_Next_Generation_Mobile_Rendering-2033767592.pdf

你可能感兴趣的:(TA,unity,性能优化,xcode)