🥍

Skinned Animation Implementation 蒙皮动画

How to Animate a Mesh

notion image
📖
给你一个模型(mesh),如何让它动起来?
  • 先做一个模型,也就是一个 mesh 在它的 bonding pose。
  • 去做一个骨骼,这个骨骼就跟人类的骨骼一样,它就内嵌在我们的皮肤之下。它跟是 mesh 正好是可以 align 到一起去的。
  • 给它刷上它的蒙皮,就是告诉每一个顶点该受哪个骨骼的影响:哪个骨骼对你的影响大一点,哪个骨骼对你影响小一点。
  • Animator 开始让这个骨骼动起来。
  • 顶点刷了蒙皮,有权重,它们就也就跟着动了。
💡
蒙皮动画的原理非常简单,但是如果真的开始动手去写这个代码,我们看到的情况大部分时候是这样的:看到那个骨骼真的在动了,但是那个角色他就一直傻站在这,或者是整个角色动就动飞掉了。 如果动画系统的数学公式没有完全推导对,公式算错或者矩阵算错了,就会出现把模型给写炸掉的情况。
💡
把蒙皮动画的数学和它的实现整个搞明白之后,后面去理解 IK ,理解其他的一些动画的时候,其实难度就不大了。
 

Different Spaces

notion image
在世界坐标系里,我们可以做得非常非常大,可以到一个整个太阳系的距离。
对于每一个角色来讲,实际上我有一个自己的坐标系,叫做模型坐标系。
做动画的时候在模型之上还有一个坐标系是 Local Space 局部坐标系。
每一根骨骼都会发生平移、旋转,相对于模型坐标系移动。在动画里面每一根骨骼,每一根骨骼它自己会发生平移、旋转,这样就会相对于模型坐标系发生变化了。每一个骨骼它的这个标价,它的这个坐标系都是不一样的。在根部一路往手指尖先去传递的时候,这个方向就可能发生很大的变化,方向是一个一个骨骼传递过去的。
整个动画数据的表达,实际上是在局部坐标系的。如果把局部坐标系一路从根节点积分上来,才能算成它的模型坐标系。把模型坐标系考虑到角色它自身站的位置和旋转,才能把它变成世界坐标系。只有把它变到世界坐标系的时候,这个角色才会被渲染。
💡
我们理解了三个坐标系的概念,就可以开始给这个角色构建它的骨骼了。

Skeleton

Skeleton for Creatures

notion image
对于类人型生物,两足生物(biped),我们会构建一个跟人类的骨骼非常接近的骨骼结构。
其树状结构的起点一般是在人的跨部 Pelvis,脊椎的最后一根骨头。因为那个点出去之后,它向下可以分化出两条腿,向上就是脊椎,脊椎决定你的身体是弓的还是直的。然后到肩膀一展开,它就变成两只手。这个结构对于很多游戏里面的大部分应用是够的。
 
对于小动物,小动物可以趴在地上,他们的骨架就又不一样。
💡
为什么我们要定义标准的骨骼呢? 因为当我们去生产动画资源的时候,特别是去做 motion capturing 的时候,包括资源的生产的时候,我们不太可能去生产那么多不一样的资源。如果骨骼基础结构老是在变,动画师是很难做的。如果骨骼的 top 老是在变,一会以脚趾头为起点,一会以脑袋为起点,那这些动画资源之间就很难复用。所以这个基础结构也是我们这个行业约定俗成的结构。

Joint vs. Bone

  • The joints are the objects directly manipulated by the animator to control motion
  • The bones are the empty space between the joints
Skeleton 在游戏引擎里面,我们真正表达的并不是骨骼,而是 Joint(关节)。
notion image
例如,我们存储肘关节的数据,因为肘关节前面连了个刚体的一个骨骼,所以当肘关节发生旋转变换或平移的时候,骨骼也会跟着动,但是我们并不会直接存储骨骼的数据。
肘关节的下一个子节点就是手腕关节,这个手腕关节就控制了我的手掌。所以在游戏引擎里面的骨骼,实际上是 Joint 关节,两个关节之间定义了一个 Bone 骨头。
Joint 本身是有很多的自由度,但是 Bone 没有,因为 Bone 是由两个 Joint 联合在一起定义出来的。
当我们翻转手掌的时候,twist 扭转(肌肉扭转)的其实是两个关节之间的 Bone 骨头,Joint 是刚体是不会被 twist 的。

Humaniod Skeleton in Real Game

notion image
除了人正常要的骨骼之外,一般游戏引擎里,还需要加很多很奇怪的骨骼:
  • 例如标准骨骼大概有一百个左右,但为了人脸的表情细节,还会加很多骨骼,来驱动眼睛眉毛的运动、眼球的运动,下节课会讲。
  • 游戏中还会对人有很多的变形,比如说要给它做个大翅膀、斗篷,还有我手上拿武器等等,这些骨骼实际上都会加到角色的标准骨骼上面。
💡
当我们想做一个游戏引擎的时候,首先第一步就要定义标准角色的标准骨骼是多少。这个是 TA 团队和技术团队最核心的要确定的东西。

Joints for Game Play

notion image
比如说手上拿了个大斧,武器有一个武器 Mount 骨骼。无论拿了个巨剑还是大斧,实际上武器是 Mount 到手上一个叫 weapon joint 上面的。看起来好像两只手拿了武器,但一般很多动画师是把这个武器单一的绑在手上的武器骨骼上的。人骑乘在生物上面,也会有一个骑乘 Joint。

Root Joint

notion image
我们虽然很多骨架是从尾椎 Pelvis 开始。但是在真正的游戏引擎里面,我们一般会定义一个叫 root 的骨骼。 以人形为例,我们会定义 root 骨骼在两脚中间。比如我们在游戏里面表达一个角色的移速,或者跳跃 0.5 米,我们更关心的离地高度是从脚开始的,所以用 root 的骨骼会更加的自然。
notion image
表达四肢动物时,它的 Pelvis 骨骼所在的位置其实非常有意思。想象一下,一个演员如果想让扮演一匹小马的话,他的尾椎正好在马的屁股那边。所以动画师一般会把他的 Pelvis 骨骼放到他的尾椎的地方,但是它的 root 会放到马的肚子下面,因为这样方便表达马的位置。
💡
前面讲 Gameplay 系统的时候,讲到 Object 之间是有父子关系的,为什么我们需要有父子关系呢? 因为在游戏中有很多动画,很多角色之间的行为是绑定的,最经典的就是人骑在马上。

Bind Animation for Objects

notion image
骑马的时候,是人播放人的动画,马播放马的动画,怎么让它的动画一致呢?
人和马各有 Mount Joint,那就把这两个 Joint 重合在一起。不仅是一个简单的空间的重合,旋转也重合,可以想象成它们坐标系的轴是整个对接在一起的。当马前倾的时候,人要跟着前倾,后仰的时候也一起后仰。这就实现了 Bind Animation。
💡
角色是怎么开汽车呢? 例如游戏里看到角色拉开车门翻身跳进车里面很厉害,但是它总有那一帧,这个角色的屁股会绑死在那个汽车的某一个座椅上,然后车怎么动人就跟着动了。这其实就是游戏引擎里面如何实现这个绑定动画的原理,这个也是引擎必须要实现的一个功能,它一般会和 Gameplay 系统连接得非常的紧。

Bind Pose - T Pose vs. A Pose

notion image
早期我们的角色绑定会用 T-pose,但是随着现在的商业游戏的发展,大家逐渐会发现就 T-pose 有问题,那就是角色的肩膀其实是会被挤压的。
比如要做一个角色肩甲的展开变形的时候,就会发现其实那里的精度不够。所以现在 3A 游戏一般越来越多的用 A-pose,整个人站得相对自然一点,那肩膀的地方就会有相对高的精度。

Skeleton Pose

notion image
当我有了骨骼,有了 mesh 之后,我们就能表达一个动作出来,这一个静止的状态就叫 Pose。把很多的 Pose 连到一起的时候,我们就会形成一个动画。
其实在真正地表达一个动画的 Pose 的时候,它是有九个自由度的。之前讲刚体的自由度只有平移和旋转。但实际上在真正做动画的时候,我们还有自由度叫 Scale 放缩。
💡
放缩在很多动画里面其实是非常有用的,比如说做人脸的变形,或一些角色弹性的一些变化的时候,我们需要用到放缩。