Vico Bill< 刘 利 波 > 的个人网站

记录关于学习、工作中的技术点滴

C,C++,Rust,Ruby爱好者;热衷于游戏开发、任务自动化与跨平台;沉迷于游戏引擎与图形表现;深信'简单、多元'哲学的力量。


访问主页

游戏引擎-Unity3D性能优化指南

总纲

性能优化无外乎两个关键概念:时间和空间。

时间,对应着速度、性能、流畅度等等。空间,对应着内存、显存等等,计算机是有限的资源,得合理利用。

工具篇

兵马未动粮草先行。工欲善其事必先利其器。性能优化,最重要的是能找到瓶颈。连瓶颈都不能找到,谈何优化?

内置工具:

内置工具包括以下几种:Profiler,FrameDebugger,

Profiler

CPU剖析

CPU性能主要衡量游戏中花费的时间,即运行速度。CPU的主要功能是解释计算机指令以及处理计算机软件中的数据。影响运行速度的性能指标包括:CPU的工作频率、Cache容量、指令系统和逻辑结构等。

当有大量逻辑需要处理,或陷入大量循环时,CPU会明显增高,即,运行速度会下降,表现为来不及处理后续任务。

CPU运算速度,直接表现是一个计算任务的执行时间。

虽然现在处理器都具有多核结构,即多CPU,能并行处理任务,但在单核机器上,CPU往往会成为游戏重要的瓶颈部分。

在渲染中,引擎部分往往还涉及到一个缓存命中率性能问题,这个问题很细微,主要是缓存的数据不匹配问题,通常通过提高Cache容量即可解决。

Unity的CPU剖析器,可探测:

  • 渲染:直接表现为帧率。帧率高且稳是最终追求。渲染部分的瓶颈,通常在渲染计算,即坐标转换、顶点裁切等计算,其中最为明显的是矩阵运算和四元组运算。这里通常涉及的直接概念是DrawCall。DrawCall是将CPU为GPU准备的数据经由总线发给GPU的过程。
  • 脚本:脚本方法调用。即Update、FixedUpdate等消耗的时间。主要涉及:游戏逻辑、输入响应、AI等
  • 物理:物料模拟等计算所消耗的时间。
  • 动画:动画蒙皮、顶点动画。
  • 垃圾回收:数据标记并回收。
  • 垂直同步:垂直同步是用于等待帧,以防止前一帧绘制完成后一帧未准备完全时,画面撕裂效果。
  • 全局光照:
  • UI:
  • 其他:

在Unity中,采用线程执行任务:主线程、渲染线程和Workers。Works中,又分了Load、Script等。

在Unity中,当出现CPU瓶颈时,通常从脚本、物理着手。

内存

内存Unity内部保留了内存池,以避免频繁询问操作系统内存。内存是与CPU沟通的桥梁。内存用于暂存CPU的运算数据以及与硬盘等外部存储器交换的数据。即,内存暂存输入数据和结果。

当内存明显增大时,很大部分原因是:内存分配过多。最大的直接原因是大量new对象,并且不释放。对于Unity而言,则通常是资源加载过多。

内存的优化手段主要着手点是资源量。资源包括两种:内部资源和外部资源。内部资源是在程序内部new的资源,如变量、常量等。外部资源则包括:纹理、材质、网格、音频、视频、动画、资源包等。

  • 总分配内存
  • Texture内存
  • Mesh内存
  • Material 数量
  • 对象数量:创建的对象总数。如果此数字随时间而增高,则游戏会创建一些永不销毁的对象。
  • 总GC已分配内存
  • GC已分配

在Unity中,出现内存瓶颈时,通常采用:压缩、卸载、内存池等方式。

GPU:硬件渲染计算

GPU与CPU类似,主要衡量标准是频率、缓存容量。主要用于将显示信息进行转换并向显示器提供扫描信号以显示内容,承担显示图形的任务。

GPU的数据来源于内存,即CPU计算之后,将需要处理的顶点信息通过DRAW CALL传递给GPU,GPU再进行处理。GPU基于大吞吐量设计,有很多算术计算单元可快速完成计算和很少的Cache。缓存服务于GPU线程。

GPU是渲染管线中非常重要的部分,主要处理两种Shader:Vertex Shader和Fragment Shader。主要涉及的计算包括:顶点变换、光照、贴图、混合等。

Unity的GPU Profiler能探测:

  • Opaque:不透明物体
  • Transparent:透明物体
  • Shadow/Depth:阴影和深度
  • Defferred PrePass:
  • Defferred Lighting:延迟光照
  • PostProcess:后期处理
  • Other

GPU的瓶颈,主要是与渲染有关,往往从顶点数、透明物体渲染、阴影、后期处理、动态光等方面着手。

Render 渲染

Unity的渲染Profiler主要涉及:

  • Batches:批次
  • SetPass Calls:
  • Triangles:面数。
  • Vertices:顶点数。

Audio & Video:音频与视频

Unity的Audio Profiler主要涉及:

  • Playing Sources:当前播放的音频源数
  • Audio Voices:播放的
  • Total Audio CPU:处理Audio所用CPU
  • Total Audio Memory:音频资源所占内存空间。

音频处理通常在子线程中。音频一般不易出现问题,当出现瓶颈时,可从同时播放的音频源数量、混响效果、音频文件大小几个方面着手。

Physics

Unity的物理Profiler主要涉及:

  • Active Dynamic
  • Active Kinematic
  • Static Colliders
  • Rigidbody
  • Triggle Overlaps
  • Active Constraints
  • Contacts

Global Illumination 全局光照

全局光照主要涉及的就是间接光照, 是各种光的混合计算。主要涉及:

  • Total CPU:计算GI使用的CPU。

  • Light Probe:光探针,是让动态物体从场景接收间接光的有效方法。是一种廉价的间接光计算方式。这里是探针更新所需时间。

  • Setup:在安装阶段所花时间。

  • Environment:处理环境光照的时间。

  • Input Lighting:处理输入照明所花时间。

  • Systems Time:更新系统所需时间。

  • Solve Tasks:运行光能传递解算任务所花时间

  • Dynamic Objects:更新动态对象所花时间

  • Other Commands:处理其他命令所花时间

  • Blocked Command Write:在阻塞状态下所花费的时间。

外部工具

特定平台的工具:

  • iOS:Instruments和XCode Frame Debugger
  • Android:SnapDragon Profiler
  • IntelCPU/GPU:VTune和Intel GPA
  • PS4:Razor和VR Trace
  • XBox:Pix

Memory Profiler

可识别重复资产

RenderDoc

VS Graphics Debugging

附:优化执行标准

贴图:

  • 贴图大小控制为2的幂,以避免GfxDriver对其进行分割为2份
  • 不超过1024*1024
  • 压缩,去除ALPHA通道
  • 合理合为贴图集,合并载入
  • 不同设备使用不同纹理贴图

模型:

  • 缩减面数
  • 使用LOD/AutoLOD
  • 使用的Material数量
  • 如果不通过脚本修改Mesh,则禁用其Read/Write Enabled标志
  • 禁用非角色模型自动添加的Animator组件。
  • 尽量压缩网格
  • 如果无需阴影,则禁用之

动画:

  • 动画压缩
  • 减少骨骼

声音:

  • 压缩
  • 尽量使用单声道、低比特率的音频

场景:

  • 尽量使用static物体和光
  • 如果可以避免,则去掉动态光。
  • 尽量使用统一缩放
  • 尽量使用Prefab
  • 尽量降低Camera视野距离
  • 尽量使用遮挡剔除,剔除无用渲染对象。
  • 场景顶点数保持3M以下,移动设备上少于1M。
  • 尽可能共用材质。
  • 如果不需要,则去掉Fog。
  • 物体挂载的组件如无用则删除
  • 限制Light Probe数量
  • 在光照变化大的地方(如屋前)使用LightProbe,其他地方尽量避免使用LightProbe
  • 尽量使LightProbe稀疏分布,并在地面上
  • 使用的Material尽量启用GPU Instancing。

资源:

  • 按需加载,不用则卸载,通用则按需常驻
  • 减少常驻内存资源量

物理:

  • 合理降低碰撞精度
  • 最小化碰撞检测请求
  • 合理设置碰撞矩阵,去掉无用碰撞层
  • 尽量避免使用MeshCollider

AI:

  • 合理设置AI更新频率

代码:

  • 避免新对象的频繁创建。
  • 使用内存池复用内存
  • 预先获取并引用。暂存中间值,再使用。
    • Component
    • Obect
  • 删掉无用/空消息函数
  • 多使用for,尽量避免foreach循环。foreach通过迭代器遍历,如果是值类型,则会产生装箱拆箱操作。
  • 无用对象,及时销毁或SetActive(false)
  • 尽量使用IL2CPP
  • 注意,禁用对象,不会停止协程
  • 尽量使用协程,并且避免让协程阻塞
  • Collection的Clear清除值,但并不释放内存。尽量重用它们。
  • 在性能敏感的地方,尽量最小化使用闭包。
  • 尽量避免装箱拆箱(值类型和引用互相类型转换)
  • 使用正则表达式的实例.Match函数,而非静态函数
  • 尽量使用ScriptableObject进行数据配置
  • 多使用数字hash或ID
  • 多使用非分配内存版本的函数(函数调用过程中,不产生临时内存)实现相同功能
  • 尽量在密集循环将对象与null比较
  • 计算中,优先计算简单数学运算(整数>浮点>向量>四元组>矩阵)
  • HTML格式颜色和本机Color互换,使用ColorUtility
  • 尽量消除Find、FindObjectByType
  • 使用Camera.main时,考虑在Start将其暂存。Camera.main在内部使用了FindObjectWithTag
  • 尽量使用type[x][y] 而非type[x,y],如果可以用一维数组代替,则使用一维数组
  • 尝试使用粒子系统池
  • 考虑使用UpdateManager,集中管理Update进行分发,这样通过代理按需调用Update可减少很多不必要的调用
  • 考虑使用快速插入/删除的数据结构替代delegate
  • 尽量为每个Thread任务设定相应的优先级
  • 多使用Culling Group组织群集对象移动
  • 注意虚函数和接口方法不能内联,因此注意尽量降低方法调用的开销
  • const可在编译时内联,会将引用替换成值,因此不适用于大对象。在const不适合的地方,使用static only。

Shader:

  • 尽量使用Mobile里的Shader
  • 尽量使用贴图混合替代多通道计算
  • 多使用half/fixed
  • 尽量避免pow,sin,cos,tan,log等复杂运算函数
最近的文章

【DailyGfx】1-渲染管线

渲染管线是整个渲染中的核心部分。渲染管线的主要目的是将3D描述的世界绘制成2D屏幕上显示的像素。可想而知其中的复杂度。渲染管线在早期通常实现在CPU端,称之为软渲染,即整个渲染管线的处理都交给CPU处理。软渲染可行,CPU不仅要处理游戏逻辑,还要处理渲染逻辑,其负重极大,影响FPS(Frame Per Second),对于游戏而言,帧率非常重要,需要达到30-60FPS才能让玩家感受到画面的流畅度。在VR上,则要达到90FPS。如今很少有直接使用软渲染(Software Renderer)...…

继续阅读
更早的文章

编程语言-通用编码约定

编码约定编写代码归根结底,是编写文字的过程,与写作类似。通用的术语和通用的处理方式,能快速进行多人协作的融合。前言 警告: 如果已经存在编码风格,请遵守原有编码风格。所有代码文件必须保存为**utf-8**格式。(Visual C中使用UTF8-BOM)编码风格的意义在于: 降低软件管理成本:软件的维护,基本以阅读代码为主 提升软件质量:软件同行审查主要检测代码缺陷。代码风格一致可让同行更易接纳、理解、吸收,提高缺陷审查过程效率。 降低软件复杂度:最小化代码复杂度的方式是——直接,...…

继续阅读