Unity 动画系列七 Layer分层 和 IK

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

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

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

  • 人物动画状态涉及分部并行逻辑,例如枪战游戏,腿部动作由键盘控制,而上半身动作需要由鼠标控制进行IK瞄准,以及武器切换
  • 存在状态的融合/叠加效果,例如在正常走动的基础上,搬运物品时,上半身需要持握物品并进行IK绑定,下半身仍是正常走动
  • 动作游戏中,为减轻动画师工作量,取用不同动作的部分进行组合
一、Layer
1.管理Layer

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


image.png
  • Weight 这一层的权重,0代表该层权重是0(该层不生效),1代表该层权重为1(该层中的动画能完全表现)。混合权重值可以通过代码进行动态获取、修改,例如我们根据人物的HP值,逐渐将正常走动效果,转为受伤的走动效果
animator.GetLayerWeight(layerIndex);
animator.SetLayerWeight(layerIndex, weight);
  • Mask 设置该Layer能控制身体的哪部分,设置后该Layer上面会显示一个M的小图标。详细笔记见下方
  • Blending 和其他层混合的模式。
    • Override 覆盖上面的层中对应Mask的部位
    • Additive 会加在之前Layer的动画之上。
  • Sync 复用其他层中的状态机。详细笔记见下方
  • IK Pass 什么是IK?得问问大智
二、Avatar Mask

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


image.png

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

  • 通过Humanoid身体映射图
  • 通过Transform层次结构选择包含或不包含的骨骼
1.Humanoid

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

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

  • 头部
  • 左臂
  • 右臂
  • 左手
  • 右手
  • 左腿
  • 右腿
  • Root(脚下的圆形阴影部分)

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

2.Transform

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

  • 选择一个Avatar(动画模型的Avatar)
  • 点击Import Skeleton按钮。avatar的层级结构会显示出来。
  • 可以设置对应的骨骼是否受动画控制
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复选框。

  • 不选中时,Sync出来的Layer中每一个State的动画长度会变为源Layer中的时长。
  • 选中时,根据Weight调整动画时长,Weight为1时使用Sync Layer中的动画时长。

镜像层与源层的状态,动画组,过渡设置是被绑定在一起的,修改一方会同时影响另一方。在游戏运行时,镜像层会与源层保持状态的同步(按照其中一方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,则完全用原来动画中的位置和旋转。至少要设置第一个参数,后面的几个参数都有默认值,但是你也要了解所有参数的含义:”

  • Weight 全局权重,后面所有参数的系数
  • bodyWeight 身体权重,身体参与LookAt的程度,一般是0
  • headWeight 头部权重,头部参与LookAt的权重,一般是1
  • eyesWeight 眼睛权重,眼睛参与LookAt的权重,一般是0(一般没有眼睛部分的骨骼)
  • clampWeight 权重的限制。0代表没有限制(脖子可能看起来和断了一样),1代表完全限制(头几乎不会动,像是固定住了)。0.5代表可能范围的一半(180度)。

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

  • 需要勾选对应Layer的IK Pass选项(在Layer的设置里)。
  • 代码需要写在OnAnimatorIK这个事件方法里面。
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枚举类型,包含:

  • LeftFoot 左脚
  • RightFoot 右脚
  • LeftHand 左手
  • RightHand 右手

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));


    }
}
分层后绑定效果正确
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容