自己收藏

Unity 动画系列七 Layer分层 和 IK

2021-12-01  本文已影响0人  合肥黑

参考
Unity动画系统详解7:Layer是什么?
学习笔记 --- Unity动画系统

“大智,我的Animator状态机里面的状态很多,现在太难管理了。我有很多姿势,比如不拿枪,拿手枪,拿冲锋枪,拿步枪,每种姿势都要做一个混合树,现在再去加蹲的状态,简直爆炸,有没有什么好办法?”
“那你想想这些动画之间有没有什么相似的地方可以提取出来的?”
“要说相似的话,它们腿部的动画是类似的,不管拿枪姿势如何,腿部都是一样的,只有胳膊的动画不太一样。”
“嗯,这你就算说到点子上了!这时候可以用下Animator Controller中的Layer
有了Layer,我感觉我的动画可以分为上半身和下半身两个Layer,这样一来就不需要对每个拿枪的动作设置移动的混合树了,应该能简化不少工作。”
“确实是这样,合理利用Layer不仅可以减少动画师的动画制作工作量,还能在性能上对游戏进行优化。”
使用Layer可以用来管理角色的不同身体部位。比如下半身用于行走或跑步,上半身用于射击或投掷物体。

使用动画分层的应用主要是以下情况

一、Layer
1.管理Layer

点击加号可以添加一个Layer。点击Layer旁边的齿轮图标,会弹出一个小窗口,可以设置该Layer对应的参数。


image.png
animator.GetLayerWeight(layerIndex);
animator.SetLayerWeight(layerIndex, weight);
二、Avatar Mask

Avatar Mask,替身遮罩体,可以作为一种资源文件在Assert目录下创建


image.png

用来设置该Layer的动画会影响角色的哪一部分的遮罩。AvatarMask有两种定义身体遮罩的方式:

1.Humanoid

如果你的动画是人形动画,建议用这一种方式可以快速设置AvatarMask

身体映射图包含以下几部分:

可以通过鼠标点击每一部分,绿色代表动画可以影响这一部分,红色代表动画不会影响。在空白处点击可以全选/全不选。手部和脚部的IK可以开关,表示该部分的IK曲线是否参与动画混合。IK又出现了,一会去问问大智

2.Transform

如果动画没有使用Humanoid,或者你想更精细的控制遮罩,你可以通过Transform层级结构来控制每一个骨骼。

3.Animation页签中设置Mask

在模型导入设置的Animation页签中,也可以设置Mask,设置后只会导入对应Mask的动画数据。

在这设置Mask的好处是:可以减少内存占用和CPU占用。因为不需要的身体部位的动画曲线不会被加载,并且不会参与计算。

三、混合示例一

我们需要实现这样的一个效果,人物能够进行正常的奔跑站立,只不过在我们摁下空格的时候,无论何种姿态,我们都希望人物能够挥舞自己的右手,而其它姿态保持原状态

1.Base Layer
在Base Layer上实现人物正常的站立行走效果
2.New Layer

在下层实现一个从空状态(不引用动画切片),到Wave动作的过渡,配置层遮罩限定右手,混合模式为Overrid绝对坐标重写


image.png
image.png
3.运行

运行动画,下层空状态时不对上层产生影响。当下层过渡为Wave挥手动作时,由于层遮罩的配置,只重写右手部分的动作,因此人物右手产生挥手动作,其它部位仍保持Base Layer的效果。

四、混合示例二

我们需要在人物原有的正常站立奔跑动作上叠加一层摆臂效果(或者其它什么效果)

1.BaseLayer和之前一样,实现人物的走动和奔跑
2.下层动画使用Addicted叠加模式,这里我们不使用层Mask,而是用ClipMask实现
image.png
image.png
3.运行效果

当下层为摆臂动作时,由于ClipMask的配置,仅双臂部分的动作被叠加到了上层。这个摆臂的动作其实是从正常的跑步动画中得到的,但由于ClipMask的配置,仅双臂部分保留了跑动的摆臂动作,其它未激活节点被恒置于Default状态。

在进行Addicted,相对运动叠加混合时,双臂部分保留的动作就被叠加了上去,而其它部分由于恒置于Default状态没有相对运动,就不会产生影响。

这里其实通过层Mask配置,而不使用ClipMask,也可以实现,并且有些人也是这么去做的。注意!!!这两种做法其实都没有问题,针对这个简例也没有孰优孰劣之分。但我这里就是要借故去提一下ClipMask,去用一下这种做法,告诉你ClipMask可以这样用,以免参与到一些网课老师以及博主的,冷落ClipMask的队伍中去。

五、Sync 复用其他层中的状态机

有时能够在不同的Layer重用其他Layer的状态机非常有用。比如你想模拟一个“受伤”的状态,你有受伤状态的各种动画比如走跑跳。这时候你可以选中Sync复选框,选择你想要同步的Layer。状态机的结构会保持一致,但是可以设置不同的Animation Clip。选中时,Layer旁边会有一个S小图标。

这意味着,同步出来的Layer不需要再去定义状态机,并且源Layer中状态机的任何变化都会同步到这一同步Layer。唯一需要你做的就是设置每个State中使用的动画。

Sync复选框旁边还有一个Timing复选框。

镜像层与源层的状态,动画组,过渡设置是被绑定在一起的,修改一方会同时影响另一方。在游戏运行时,镜像层会与源层保持状态的同步(按照其中一方Clip的时间为标注,对另一方的时间进行控制)。但不同的是,镜像层可以在动画状态中,引用与源层不同的动画片段。从而我们可以通过镜像层,对一些同步执行,动作不一,可复用状态配置的分层动作可以通过镜像层进行快速的创建。例如对之前的挥手效果,我们创建一个镜像层,引用挥舞另一只手的动画片段。


image.png
六、IK

参考
Unity动画系统详解8:IK是什么?
学习笔记---3dMax动画系统(机械、角色动画篇)

1.概念

大智,我看完了,这个东西还是很强大的呀,正好符合我的需要。不过这里面有好多地方提到了IK,这个IK是什么东西呢?”
“IK是Inverse Kinematics的缩写,也就是反向动力学。”

“那是不是还有正向动力学?”
“没错,你都会举一反三了!大多数动画是通过旋转骨骼来实现的。子骨骼的位置跟着父骨骼的旋转而改变,因此关节链的终点可以根据前面的各个骨骼的角度和相对位置确定。这种构成骨骼动画的方法称为正向动力学。”

“那反向动力学是不是就是根据骨骼的最终节点,反向推算之前的骨骼节点的位置?”
“哟,不错哦,有长进。正如你所说的一样。有些时候我们需要根据空间中的位置来确定骨骼节点的位置,比如让角色拿枪,不同的枪可能握持的位置不太相同,就需要根据握持的位置来决定角色手的位置。Unity中的IK支持所有人形动画。”

其他的用途其实还有比如:角色的头的旋转,这样可以和你视角的方向一致。角色的脚的位置,这样可以让角色踩在地面更贴合。”Unity中IK能设置的部位就是5个,分别是:头、左右手、左右脚。所以没有其他部位的IK了,我们常见的其实也都是这些。

“这样说我就有些明白了,Animator中的State设置中的Foot IK是不是就是设置脚部受IK的影响?”
“是的。”

“那Layer中的IK Pass是什么意思呢?”
“选中IK Pass的时候,每帧会调用脚本中的OnAnimatorIK方法,可以在这个方法中动态设置IK。

2.如果对反向动力学还是不太理解,可以参考Inverse Kinematics 逆向运动学

逆向动力学的过程在一些场景下十分有用,例如一个机械臂,你需要它去抓取一个放置在特定的空间位置的物体,那么就需要利用逆向动力学去计算出机械臂各个关节的旋转角度,进而驱动机械臂去抓取物体。此外,在游戏编程以及CG动画制作等领域,逆向运动学也扮演这非常重要的角色。

为了使得以上的描述更加形象,这里举一个2D逆向运动学的例子:

给定两段长度固定的刚性关节,并固定住了一个关节作为起点,最后给出一个固定的目标位置(target)。此时只能对两个关节进行任意角度的旋转,来让关节的末端达到给定的目标位置,下面的图例中给出了无解,唯一解,两个解的情形。


无解,关节长度不够,无论如何旋转都无法让末端达到目标位置。
一个解,任何除此之外的旋转角都无法让末端达到目标位置。
两个解,此外任何其他的旋转角度都不能使得末端达到目标位置。
当有2个以上的关节时,会有多个解。

但是,某些解决方案将比其他解决方案更好。 例如,若结构代表一个动画人物的手臂,则某些解看起来会更舒适和自然,而另一些解则会显得僵硬及不合理。 所以通常会有一个最佳解决方案。

3.设置头部IK
image.png
“那Layer中的IK Pass是什么意思呢?”
“选中IK Pass的时候,每帧会调用脚本中的OnAnimatorIK方法,可以在这个方法中动态设置IK

大智:“我们先来看看如何设置人物的头部根据视角旋转。需要用到这两个API:

public void SetLookAtPosition(Vector3 lookAtPosition);

“这个方法用来设置头部看向的位置,比如看向你左边的窗户,头就会相应的旋转。”
“这个看起来很简单嘛。”
“对,这个方法确实很简单,不过还有另外一个:”

public void SetLookAtWeight(float weight, float bodyWeight = 0.0f, 
float headWeight = 1.0f, float eyesWeight = 0.0f, float clampWeight = 0.5f);

“这个方法用来设置IK的权重,这个IK会和原来的动画进行混合。如果权重为1,则完全用IK的位置旋转;如果权重为0,则完全用原来动画中的位置和旋转。至少要设置第一个参数,后面的几个参数都有默认值,但是你也要了解所有参数的含义:”

大智:“有了这两个方法你就可以实现头部的IK了,不过还有两点需要注意:”

void OnAnimatorIK(int layerIndex)
{
    _animator.SetLookAtPosition(pos);
    _animator.SetLookAtWeight(1);
}

上面的代码就是人物的头部看向一个位置的代码。需要注意的是这个OnAnimatorIK方法有一个参数layerIndex,这个就是对应的Layer的序号,只有勾选了IK Pass的layer才会调用到这个方法里,每个勾选了IK Pass的layer调用一次。

小新:“这样我就能实现人物的头跟着视角移动了,哦也”
大智:“是的哦”

4.设置手脚IK

小新:“那手脚的IK是不是也跟这个类似的?”
大智:“是的,手脚的IK是和这个类似的,不过API有些不一样,我们来看看”

public void SetIKPosition(AvatarIKGoal goal, Vector3 goalPosition);
public void SetIKRotation(AvatarIKGoal goal, Quaternion goalRotation);

设置头部时,因为头不会移动,所以只需要设置LookAt的位置,头部跟随旋转即可。
但是对于手和脚,需要同时设置位置和旋转。

goal AvatarIKGoal枚举类型,包含:

goalPosition/goalRotation IK目标位置/旋转

同样还有设置权重的API:

public void SetIKPositionWeight(AvatarIKGoal goal, float value);
public void SetIKRotationWeight(AvatarIKGoal goal, float value);

goal AvatarIKGoal枚举类型
value IK的权重,1代表完全使用IK值,0代表使用原动画的值

常见的设置手部IK的代码是(一般需要4行代码设置一个部位):

void OnAnimatorIK(int layerIndex)
{
    _animator.SetIKPosition(AvatarIKGoal.LeftHand, position);
    _animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);

    _animator.SetIKRotation(AvatarIKGoal.LeftHand, rotation);
    _animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
}

人物左手与球体的绑定示例:


这里也可以在球体下挂一个空节点,将手绑定到空节点上,调整空节点进行偏移,从而让手能够接触到球体
    public Transform IKBall_1;//绑定目标
    public Vector3 IKMove_1;//偏移量

    private void OnAnimatorIK(int layerIndex)
    {
        if (layerIndex == 0)
        {
            if (animator.GetCurrentAnimatorStateInfo(0).shortNameHash == idleHash)
            {
                animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);
                animator.SetIKPosition(AvatarIKGoal.LeftHand,IKBall_1.position + 
                    transform.forward * IKMove_1.z + transform.right * IKMove_1.x + transform.up * IKMove_1.y);
                animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
                animator.SetIKRotation(AvatarIKGoal.LeftHand,
                    Quaternion.LookRotation(transform.forward, -transform.right));

            }
        }
    }
5.IK位置/旋转调节小技巧

小新:“大智,这个IK的位置好难调整啊,我想让角色拿枪的手能够贴合这个枪,有没有什么简单的办法?我这调了一个多小时了,还不是特别完美。。。”
大智:“调IK是个慢活,不过呢,确实有一些小技巧在里面。IK相关的代码涉及到位置和旋转,这时候不要傻傻的直接定义一个位置和旋转来手动设置,最好的办法是设置两个参照物,作为IK的位置和旋转的参考,这样只需要调这两个参照物就可以了。”
小新:“对对对,这样的话就不用去修改位置和旋转的值,而是直接修改这俩参照物的位置和旋转就可以了。我来试一下。”

eaf7d560-233b-11eb-8118-c2cbf65660a4 00_00_00-00_00_30~1.gif
小新:“太棒了,这样我就能在运行时调整这个参考位置,调到一个完美的位置和角度。”
小新三下五除二,就调到了一个合适的位置和角度。
“调好了!”小新高兴地喊道,随即退出了Play状态。
大智:“高兴早了吧?你这么就退出来了,修改的能保存下来么?” 记好了,点击Transform组件右上角的小图标,可以Copy Component,在运行时点击,退出运行后,再点击小图标,选择Paste Component Values,这样就可以将数据粘贴回来了。”
6.关节绑定

关节绑定是在使用四肢绑定,产生了人物手/脚部对目标位置绑定的基础上,对人物的肘关节/膝关节进行位置绑定。注意!!!必须在使用了对应的四肢绑定,才能令关节绑定生效,不能单独进行关节绑定(必须绑定了左手位置,才能对左臂肘关节进行绑定)

关节绑定的方法:

animator.SetIKHintPositionWeight(AvatarIKHint, Weight);
animator.SetIKHintPosition(AvatarIKHint, Position);

其中AvatarIKHint是一个枚举类型,包括人物的左右肘关节,左右膝关节


image.png

通常我们会根据四肢绑定的目标位置,结合人物的Transform物体坐标,按一定的算法/偏移量去推算关节绑定的位置。例如下面的示例代码,让人物踩在圆球上并保证小腿竖直:


image.png
    public Transform IKBall_1;//目标球体


    public Vector3 IKMove_1;//右脚绑定偏移量
    public float kneeMove;//膝关节偏移量


    private void OnAnimatorIK(int layerIndex)
    {
        if (layerIndex == 0)
        {
            if (animator.GetCurrentAnimatorStateInfo(0).shortNameHash == idleHash)
            {
                animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1);
                animator.SetIKPosition(AvatarIKGoal.RightFoot, IKBall_1.position +
                    transform.forward * IKMove_1.z + transform.right * IKMove_1.x + transform.up * IKMove_1.y);

                animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1);
                animator.SetIKRotation(AvatarIKGoal.RightFoot, Quaternion.LookRotation(transform.forward, Vector3.up));

                animator.SetIKHintPositionWeight(AvatarIKHint.RightKnee, 1);
                animator.SetIKHintPosition(AvatarIKHint.RightKnee, IKBall_1.position +
                    transform.up * (IKMove_1.y + kneeMove) + transform.forward * IKMove_1.z + transform.right * IKMove_1.x);

            }

        }
    }
7.IK的相互影响

如果IK方法与方法之间会产生相互影响,那么它们不能被置于同一个层中。

相互影响是指一方的IK绑定,会导致另一方需要绑定的位置有所改变。这是由于进行IK绑定的瞬间,IK效果并不会立即被体现出来,导致一方对另一方绑定位置的影响具有滞后性,导致后绑定的一方绑定位置出错。

如果你分别启用两端IK绑定,效果无误,而在同一层中同时启用时出错,那么就可以断定是相互影响的滞后性导致的。此时我们可以创建一个空的动画层并勾选IK Pass用于运行IK逻辑,在OnAnimatorIK中使用逻辑分支在不同的层中运行两段逻辑。

例如人物的注视+手部绑定,由于绑定位置是头部下方的一个节点,导致同时启用时出错


本例中为实现一个人物肩扛圆柱体对准球体的效果

此时我们可以创建一个空层开启IKPass,并在Animator中对不同的分层分别使用两端绑定逻辑

if (animator.GetCurrentAnimatorStateInfo(0).shortNameHash == idleHash)
{
    if (layerIndex == 0)
    {
        //注视绑定
        animator.SetLookAtWeight(1, 1, 1, 1, 1);
        animator.SetLookAtPosition(Aim.position);

    }
    else if (layerIndex == 1)
    {
        //双手绑定
        animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);
        animator.SetIKPosition(AvatarIKGoal.LeftHand, IKBall_1.position);
        animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
        animator.SetIKRotation(AvatarIKGoal.LeftHand, 
            Quaternion.LookRotation(transform.forward, transform.TransformDirection(new Vector3(-1f, -1.73f, 0f))));

        animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
        animator.SetIKPosition(AvatarIKGoal.RightHand, IKBall_2.position);
        animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);
        animator.SetIKRotation(AvatarIKGoal.RightHand, Quaternion.LookRotation(transform.up, transform.right));


    }
}
分层后绑定效果正确
上一篇下一篇

猜你喜欢

热点阅读