Unity作为一款强大的游戏开发引擎,其动画系统提供了丰富的功能,但在开发大型或复杂游戏时,动画系统的性能优化变得尤为重要。本文将详细探讨Unity3D动画系统的兼容优化技术,包括技术详解和代码实现。
动画(Animation)
Animation
- 播放单个AnimationClip速度,Legacy Animation系统更快,因为老系统是直接采样曲线并直接写入对象Transform
- 针对动画的缩放曲线比位移、旋转曲线开销更大
- 常数曲线不会每帧写入场景,更高效
Animator
- 使用哈希而不是字符串来查询 Animator。
- 使用曲线标记来处理动画事件
- 使用Target Marching函数来协助处理动画
- 将Animator的CullingMode设置成Based On Renderers来优化动画,并禁用SkinMesh Renderer的Update When Offscreen属性来让角色不可见时动画不更新
Animator VS Animation
- Animation可以将任何对象属性制作成Animation Clip, Animator是将Animaiton Clip组织到状态机流程图中使用
- Animation与Animator播放动画时的效率是有个临界点的,这个临界点是根据动画曲线条数来的,当动画曲线条数小于这个临界点时Animation快,当动画曲线条数大于这个临界点时Animator快
- 当Cpu核数较少时,Animation播放动画有优势,当Cpu核数较多时,Animator表现会更好
- Animator Controller Graph中的所有动画节点的Animation Clip都会载入到内存中,当有海量动画状态机节点时,内存开销较大
Playable API
Playable API是以一套树形结构来组织数据源,并允许用户通过脚本来创建和播放自定义的行为,支持与动画系统,音频系统等其他系统交互,是一套通用的接口。
Playable API VS Animator
Playable API优点
- 支持动态动画混合,可为场景中的对象提供自己的动画,并可以动态添加到PlayableGraph当中使用
- 允许创建播放单个动画,而并不会产生创建和管理AnimatorController资源所涉及的开销,可更加灵活的控制PlayableGraph的数据流,可以插入自定义的AimationJob。
- 可以控制动画文件加载策略,按需加载、异步加载等
- 允许用户动态创建混合图,并直接逐帧控制混合权重(甚至可以混合AniationClip与AnimatorController动画)
- 可以运行时动态创建,根据条件添加可播放节点。而不需要提前提供一套PlayableGraph运行时启动和禁用节点,可以做到自由度更高的override机制
- 可加载自定义配置数据,更加方便的和其他游戏系统整合
Playable API缺点
- 没有直接使用Animator直观
- 混合模式没有现成的,需要自己实现
- 需要开发更多的配套工具
- 有一定学习成本
解决方案选择
一些简单、少量曲线动画可以使用Animation或动画区间库如Dotween\iTween等完成,如UI动画,Transform动画等。
角色骨骼蒙皮动画如果骨骼较少,Animation Clip资源不多,对动画混合表现要求不高的项目可以采用Legacy Animation。注意控制总体曲线数量
一些角色动画要求与逻辑有较高的交互、并且动画资源不多的项目可以直接用Animator Graph完成
一些动作游戏,对动画混合要求较高、有一些高级动画效果要求、动画资源量庞大的项目,建议采用Animator+Playable API扩展Timeline的方式完成。
减少更新频率
默认情况下,即使动画不在屏幕上,Animator也会更新每一帧。 有一个名为Culling Mode的选项允许您更改此更新方法。
Culling Mode
- Always Animate:始终更新,即使在屏幕外(默认设置)
- Cull Update Transform:不要在屏幕外写IK或者Transform,执行状态机更新
- Cull Completely:离开屏幕时不执行状态机更新,动画完全停止
关于每个选项都有几点需要注意。首先,在设置完全剔除时,要小心使用根移动。例如,如果你有一个动画从屏幕外帧,动画将立即停止,因为它是在屏幕外。因此,动画将永远不会帧。下一步是剔除更新变换。这似乎是一个非常有用的选项,因为它只跳过更新转换。但是,如果您有抖动或其他依赖于transform的过程,则要小心。例如,如果一个角色出了帧,那么就不会从那个时候的姿势进行更新。当角色再次进入帧时,它将被更新为一个新的姿势,这可能会导致摇晃的物体明显移动。在更改设置之前,最好了解每个选项的优缺点。 此外,即使有了这些设置,也不可能动态地改变动画更新的频率。例如,您可以通过将距离相机较远的对象的动画更新频率减半来优化动画更新的频率。在这种情况下,你需要使用AnimationClipPlayable或停用Animator并调用Animator.Update 。两者都需要编写自己的脚本,但后者比前者更容易实现。
动画合批优化
动画合批:动画合批是一种将多个物体的动画合并为一个批次进行渲染的技术,可以显著减少渲染调用的次数,提高渲染效率。在Unity中,可以通过GPU Instancing技术实现动画合批处理。
GPU Instancing:GPU Instancing允许在单个绘制调用中渲染多个相同的物体,减少了CPU到GPU的通信开销。在Unity中,可以通过Graphics.DrawMeshInstanced方法实现GPU Instancing。
DOTS(Data-Oriented Technology Stack)优化
ECS(Entity Component System)架构:Unity3D的DOTS技术堆栈中的ECS架构提供了一种更高效的数据组织方式,通过组件化实体和并行处理,可以显著提高游戏的性能和响应速度。
Burst Compiler:Burst Compiler是DOTS中的一部分,可以将C#代码编译成高效的机器码,进一步提高性能。
减少Update帧率
if(Time.frameCount % 2 != 0 ) return
这里会导致2个问题,如果从骨骼拿位置在ui上显示,可能会出现卡顿的问题。
优化思路
- 骨骼动画时间
- 骨骼动画压缩精度
- 骨骼动画骨骼数量,这个是最重要的优化,删除不用的骨骼,减少不必要的骨骼,以此来减少骨骼的计算量
- CPU骨骼动画烘培成GPU顶点动画
- 若 SkinnedMeshRenderer长期固定某一姿势,BakeMesh 后用 MeshRenderer 替代
- 动态骨骼数量
- 动态骨骼物理碰撞检测
- Animator 组件 CullingMode 设置为 Cull Update Transforms 或者 Cull Completely
- SkinnedMeshRenderer 设置 Update When Offscreen
- Animator 状态机复杂度
- Animator 激活与关闭时机
- Animator Initialize
- Animation/Animator -> Tween 组件
- Animation 压缩误差值设置
- Animation Generic vs Humanoid Type
- GPU Skinning
今天是2024年12月1日
重复一段毒鸡汤来勉励我和你
你的对手在看书
你的仇人在磨刀
你的闺蜜在减肥
隔壁的老王在练腰
而你在干嘛?