Unity性能优化_动画

在项目开发过程中,动画表现需要考虑到其实现的经济性与适合的应用场景。常用的动画形式有以下几种:

  1. 简单动画-Update实现
  2. 简单动画-Tween实现
  3. Animator
  4. 顶点动画
  5. 骨骼动画
  6. 序列帧
  7. Shader UV动画
  8. 粒子特效

PS:粒子特效可以被视为动画表现的一种形式。但是由于其非线性的特点,不在这里展开描述。

一 简单动画-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动作-导出序列帧。

一些适合采用序列帧的案例有:

  1. 使用粒子特效来实现不同颜色的蝴蝶蜜蜂等微小动物。
  2. 使用骨骼动画来制作小兵动作,而小兵角色一般都有大量出现的场景。

七 Shader UV 动画

上面提到的序列帧通过GPU Instancing的实现方式,其实就是一种利用Shader UV动画原理的案例。