在项目开发过程中,动画表现需要考虑到其实现的经济性与适合的应用场景。常用的动画形式有以下几种:
- 简单动画-Update实现
- 简单动画-Tween实现
- Animator
- 顶点动画
- 骨骼动画
- 序列帧
- Shader UV动画
- 粒子特效
PS:粒子特效可以被视为动画表现的一种形式。但是由于其非线性的特点,不在这里展开描述。见文:Unity性能优化_粒子特效(Particle System)
一 简单动画-Update
通过在Update方法中进行特定逻辑的编码。比如:操作transform对象的position,localRotation,localScale。
特别需要指出的是,因为对性能要求的敏感性,核心玩法中对3D GameObject的操作应该使用最基础的实现,比如玩家角色移动,Monster对象的移动,子弹运动等。而不要使用xxxTween,降低代码自由度和可读性的同时还会带来大量的GC操作。
二 简单动画-Tween
常见的就是UI的动画,用xxxTween,比如DoTween来实现足以满足需求。
三 Animator&Animation
Unity原生组件,大家最熟悉,制作一些更为复杂的动画效果比Tween更直观,也方便团队协作。
Animator CullingMode 可见性更新
Cull Update Transformations表示如果该动画不可见,则不会渲染该动画,但是依然会根据该动画的播放来改变游戏对象的位置、旋转、缩放,是常用的选项。
Animation CullingType
一般设置成AlwaysAnimate,慎重考虑设置成BasedOnRenderers,需要更严格的实现动画状态播放逻辑。
属性访问字符串转Hash值
通过Animator.StringToHash来转换 Animator 属性名称,避免重复的Hash计算。
Animator animator = GetComponent<Animator> (); //正常情况播放动画的方法 animator.SetBool ("animName1", true); animator.SetFloat ("animName2", 1f); //优化播放的方法 int hashValue1 = Animator.StringToHash ("animName1"); int hashValue2 = Animator.StringToHash ("animName2"); animator.SetBool (hashValue1, true); animator.SetFloat (hashValue2, 1f);
检查Animator.Rebind开销
简化Animator对象,尽量在Animator中减少不需要动画的物体,减少动画中MonoBehaviour的字段数量.部分动画曲线和关键帧较多,建议去掉冗余数据,⽤⼯具压缩。
压缩动画浮点数精度
通过扫描*.anim文件
for (int ii = 0; ii < clipAnims.Length; ++ii) { ClipAnimationInfoCurve[] newCurves = clipAnims[ii].curves; //Debug.LogFormat("OnPostprocessFBX : newCurves Clip Count : {0}", newCurves.Length); if (newCurves == null) { continue; } foreach (ClipAnimationInfoCurve animCurve in newCurves) { if (animCurve.curve == null) { continue; } Keyframe[] keyFrames = animCurve.curve.keys; for (int i = 0; i < keyFrames.Length; ++i) { Keyframe key = animCurve.curve.keys[i]; key.value = float.Parse (key.value.ToString ("f3")); key.inTangent = float.Parse (key.inTangent.ToString ("f3")); key.outTangent = float.Parse (key.outTangent.ToString ("f3")); keyFrames[i] = key; } animCurve.curve.keys = keyFrames; } clipAnims[ii].curves = newCurves; }
去除Scale曲线
foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings (theAnimation)) { string name = theCurveBinding.propertyName.ToLower (); if (name.Contains ("scale")) { AnimationUtility.SetEditorCurve (theAnimation, theCurveBinding, null); } }
减低曲线精度
……
四 顶点动画
五 骨骼动画
更为复杂的动画形式,无法进行批处理,消耗更多CPU/GPU算力,一般在注重角色表现的项目中使用,可以实现角色全身的复杂动画。
Import Settings – 没有动画的模型确保关闭Rigs
Import Settings – MeshSkinning.Update时间
勾选Optimize Game Objects,可以优化⻣骼层级,减少矩阵运算。
Animation 开启动画压缩
如果Optimal压缩异常,则回退到KeyframeReduction
可见性更新
禁用 Update When Offscreen 可以减少 CPU 和 GPU 的负担,但可能是一个负优化策略。为了保持动画的流畅和一致性,需要保持该选项启用。
Skinned GPU Skinning
把骨骼矩阵存(烘焙)在配置文件里面,然后通过特殊的shader(Compute Shader),计算顶点的位置,直接在GPU端得到了网格模型的顶点在动画帧该在的位置。 这一切由于是在GPU端直接得出结果,所以根本不会产生CPU的合并和DrawCall。还能通过引入GPU Instancing技术进一步优化。
《戴森球》就是这种做法,看到有人为此开发了专门的插件。
骨骼LOD
程序化的实现情况下,动作看起来可能不自然,一些细节骨骼丢失,所以如果美术DCC多蒙几套,可能视觉还会好点。程序化的肯定是严格按某些骨骼节点结构来做,看上去应该没有美术调的过度自然。
Unity默认不支持程序化Lod,需要改到源码。当然此方案需要消耗更多的内存。
JobSystem化
https://github.com/Unity-Technologies/animation-jobs-samples
六 序列帧
预录制的动画,因为是用贴图的形式来存储不同时间点的变化信息,需要消耗更多的内存空间。
可以通过减少单位时间内的播放帧数来降低内存的占用,但是需要考虑到动画品质的要求做权衡。
一般对于一些不重要的(屏占比低,动作不复杂)3D角色可以使用少量的序列帧来处理。而由于像素尺寸小,所以可以把多个角色的不同动作状态都录制在一张贴图上,通过Gpu Instancing的形式进行批处理。
PS:三转二(3D转2D),通过建模- 绑骨骼-k动作-导出序列帧。
一些适合采用序列帧的案例有:
- 使用粒子特效来实现不同颜色的蝴蝶蜜蜂等微小动物。
- 使用骨骼动画来制作小兵动作,而小兵角色一般都有大量出现的场景。
七 Shader UV 动画
上面提到的序列帧通过GPU Instancing的实现方式,其实就是一种利用Shader UV动画原理的案例。