抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Unity 动画

整理一下先前做动画的收获

骨骼动画

  • 序列帧动画:记录动作的每一帧
    • 非矢量动画:每一帧是固定不可复用的,新的一帧本质上是完全重画一次物体,没有发生形态上的改变
  • 关键帧动画:记录动作的始末和轨迹曲线,运行时根据曲线进行插值(很显然,这是一种矢量动画)
    • 每个关键帧被称为姿势

顶点动画

骨骼动画的本质是顶点动画

  • 刚体动画:在渲染中网格不发生改变,刚体的变化矩阵发生改变
  • 顶点动画:在渲染中网格顶点发生了直接的变化(位移旋转缩放)
    • 骨骼动画:一种对顶点动画的压缩算法
    • 流体动画
    • 粒子动画
    • 变形动画:常用于制作表情,捏脸

根据顶点动画的实现方式,分为CPU和GPU

CPU动画 GPU动画
顶点位置改变时机 CPU应用阶段 GPU几何阶段
数据流 CPU传递给GPU的顶点数组发生改变 顶点着色器输出发生改变

骨骼动画

骨骼动画的模型整体不是刚体,同时为了避免旋转、移动时发生断裂,也不能将物体拆分为多个小刚体,因此只能选择顶点动画。

然而顶点动画带来的顶点移动,如果全部交由vertex着色器处理,过于昂贵,实时渲染不可接受

而且骨骼动画的顶点受更高层次的骨骼节点控制

  • 同一根骨骼的顶点要保持相对位置不变
  • 骨骼间顶点要进行平滑
  • 顶点的大体形状受骨骼形状制约,顶点变化时要保持和骨骼的联系

实现

矩阵调色板蒙皮技术(Matrix Palette Skinning):

  • 骨骼为近似刚体,其变化矩阵按顺序存储在数组中(我们称这个数组为骨骼)
  • 顶点缓冲中会存储其骨骼ID和权重信息(一个顶点通常会受1~4个骨骼影响,可以用两个Vector存储)
  • 进行变化时,顶点可以根据骨骼ID和权重查询变化矩阵,并通过插值的方式实现顶点动画(我们称查询矩阵为蒙皮)
  • CPU通过姿势间插值,以获得每一帧骨骼的位置及矩阵,GPU根据顶点信息查询矩阵进而实现运动

坐标系

img

骨骼树

三维骨骼本质上是一系列Bone组成的树状结构

在骨骼动画中,我们更关心骨骼的相对位置,于是我们选择本地坐标系(A物体的本地坐标就是以A物体中心为原点,相对于中心的偏移),并让坐标系层次嵌套

比如大臂移动时也会带动小臂移动,我们就把小臂的本地坐标系定义在大臂的本地坐标系之下,我们称大臂是小臂的父物体,小臂是大臂的子物体。而小臂在大臂的本地坐标系的坐标被称为局部坐标系(可以参考Unity的GO组织)

顺着嵌套关系向根部搜索,就可以获得物体的世界坐标系

下图为树节点的数据结构

img

  • 本地坐标系就是object space transform

在播放动画时,会从空间树的根节点(一般为盆骨节点或者root节点)开始向下递归变化,以保证父物体的local transform总是先于子物体刷新

 //对关键帧进行插值 
public OnAnimated(CoordinateTreeNode Key0, CoordinateTreeNode Key1, float t)
{
Position = Vector3.Lerp(Key0.Position, Key1.Position, t);
Rotation = Quaternion.Lerp(Key0.Rotation, Key2.Rotation, t);
Scale = Vector3.Lerp(Key0.Scale, Key1.Scale, t);

localTransform = new TransformMatrix(Position, Rotation, Scale);
combinedTransform = parent.localTransform * this.localTransform;
}

骨骼

  • 盆骨:选盆骨作为根节点(或者是空根节点的第一也是唯一的子节点),是因为盆骨在运动时相对匀速,且位置居中,可以避免骨骼树过深
  • 脊椎骨:模拟躯干运动,一般有2~3块
  • 捻度骨骼:Twist Bone,生物学中像小臂这类骨骼不是一块骨骼,而是两条并排的骨骼,以此实现肘关节不动而手掌可以旋转

img

坐标变化

  • 位移矩阵
  • 缩放矩阵
  • 旋转矩阵
  • 齐次坐标
  • 仿射变换:缩放–旋转–平移
  • 列矩阵左乘
  • 手性变换:只需要对所有的矩阵任选一维进行取反即可(哪个维度不重要,只要统一即可)

旋转

骨骼动画是矢量动画,是关键帧动画,因此会用到大量的插值,这决定了旋转的表达必须便于插值

三维空间中的点可以由三个正交向量插值表示,根据嵌套关系,一个物体发生旋转,其实就是其基向量相对于父节点基向量发生改变

双向量法

既然旋转可以由基向量的朝向表示,那么我们就直接基向量表示旋转吧!

正好三个基向量正交,而且对长度不敏感,那么我们还可以将三个向量压缩为两个向量

更进一步,这些向量都在球面上,那么用球坐标系替代直角坐标系

问题:

  • 需要时刻保证两个向量垂直
  • 不好插值

欧拉角

在航空业应用广泛,本质是一种过程量,描述了从初始位置沿着xyz轴旋转指定角度的过程,使用时需要明确旋转顺序(即顺规)

直角坐标系 欧拉角
前进方向 Z Roll 桶滚角
上方向 Y Yaw 偏航角
右方向 X Pitch 俯仰角

问题:

  • 没有统一标准,而欧拉角强依赖于顺规
  • 某些情况下会有两个轴平行,以至于失去一个自由度,导致万向节死锁
  • 不能线性插值

轴角与四元数

轴角(x, y, z, w)指沿着轴(x, y, z)旋转w度,也可以压缩为三维向量(wx’, wy’, wz’),轴角可以通过对轴向量和旋转角度分别插值对方法进行插值

四元数是一种超复数,可以用来表示旋转

四元数的可视化_哔哩哔哩_bilibili

蒙皮解算

Mesh中的顶点缓存中会存储骨架、骨骼索引,骨骼权重,运行时在顶点函数进行位置的偏移,效果表现为Mesh跟着骨架一起运动,而且在边缘处会进行变形(效果与权重分配有关),就像皮肤蒙在骨骼上一样,这个过程被称为蒙皮结算

Mesh

Motion Matching

游戏中不止使用一个动画,于是动画系统不仅需要播放动画,还需要管理动画。最常见的管理方式是数据驱动,实现形式为图状态机,通过一些bool、float、trigger数据控制动画状态

但随着角色3C不断复杂,动画系统也越发复杂,状态机里塞进了海量动画节点,他们的状态难以维护,为此育碧提出了Motion Matching

MM通过角色当前的姿势(Pose Channel)和行动轨迹(Trajectory Channel)在动画数据库中进行匹配,自动设置当前动画状态

  • Trajectory Channel通常是一个二维平面,通过人物移动速度,绘制一个移动轨迹,主要作用是跑步时转向会侧倾

  • Pose Channel是使用角色骨骼的几个节点,通常是两个脚的节点(当然你可以使用更多的节点,但随着节点数量的提升,匹配速度会更慢),主要作用是脚着地、手抓墙

骨骼动画播放

Unity Playable Script 动画播放

当我们在使用Unity Animator时,会发现我们必须要先将所需的动画片段放入Animation Controller中才能播放。如果我们想要一个Resources目录下的某个Clip,是做不到的

当游戏动画逻辑非常复杂时,状态机会非常复杂,几乎不可维护,于是很多公司会自己用Playable Script重写一份动画播放系统

Playable Script仍然是驱动Animator和Avatar的,所以角色身上仍需要Animator组件

直接播放一个动画片段

PlayableGraph playableGraph;
AnimationClip idleClip;

void Start()
{
playableGraph = PlayableGraph.Create();
playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
}

void Update()
{
if(xxx)
{
AnimationPlayableUtilities.PlayClip(GetComponent<Animator>(), idleClip, out playableGraph);

}
}

private void OnDisable()
{
playableGraph.Destroy();
}

等效于

var output = AnimationPlayableOutput.Create(playableGraph, "AnimationOutput", GetComponent<Animator>());
var clip = AnimationClipPlayable.Create(playableGraph, idleClip);
output.SetSourcePlayable(clip);
playableGraph.Play();

自行解算Humanoid

对于一个Humanoid骨骼的模型,我们可以直接遍历获得骨骼的Transform,对其进行修改

public Animator animator; 

// 遍历Humanoid所有的骨骼
foreach (HumanBodyBones bone in System.Enum.GetValues(typeof(HumanBodyBones)))
{
Transform boneTransform = anim.GetBoneTransform(bone);
// 修改 boneTransform
}

Rig

一种基于反向动力学的组件,可以覆盖动画,原理是为一个骨骼点绑定其几个父节点,并刷上相关权重,当骨骼点因为外力移动时,父节点会跟着一起运动,从而使得整体动画协调

常用于武器运动时手跟着一起动、脚着地时腿跟着一起动、头看向临近的人

参考

Blendshape动画

blendshape常用于实现表情动画、形变动画,相较于骨骼动画,十分昂贵

blendshape本质是在两个(或多个)顶点数量相同但位置不同的两个网格间插值出新网格

一个Mesh会有多个blendshape channel,每个channel都存了原始的mesh顶点buffer信息(主要是位置和法线),和目标mesh的顶点buffer信息,在解算时在两个mesh间线性插值。当有多个channel时,会按顺序依次插值

我们常用IClone和CC4制作bs模型

Unity播放bs

一个拥有bs channel的模型导入unity,会在SkinMeshRenderer上看到bs channel,直接修改这些channel就能实现动画播放

物理动画

大部分物理动画是通过解算一些物理观测点,再将这些点映射回(骨骼)动画中

Unity Magica Cloth

使用 Unity Magica Cloth插件一站式解决头发、衣服和胸部的物理模拟

Magica Cloth是Unity一个布料模拟插件,有两个版本,我使用的是基于Jobs的普通版本(2是基于DOTS的)

1. 添加预制体

在场景中拖入MagicaPhysicsManager.prefab

位置在Assets/MagicaCloth/Res/Prefab

2. 绘制骨架

使用Animation Rigging绘制骨架,方便后续配置布料

Animation Rigging – Bone Renderer Setup

3. 添加Magcia Bone Cloth

Create Others – Magica Cloth – Magica Bone Cloth

  1. 将布料的根节点拖到Root List中,通常会有多个根节点
  2. 店家Start Point Selection开始调整节点
    1. 为节点刷颜色,红色只能旋转,绿色可以运动,根节点们都要为红色(你可以先点Fill将所以节点都设为绿色,然后切换到红色笔刷状态,按住鼠标左键在场景中将根节点都刷为红色)
    2. 完成后按End Point Selection保存

cloth

  1. 调整参数(也可以直接选择Preset)
  2. 点击最下方的Create,完成创建

4. 防穿模

在模型骨架下创建Magica Collider

Unity Ragdoll Animator

Ragdoll(布娃娃系统)是一种基于物理模拟角色动作的技术,通常是通过在角色关节和躯干上放置碰撞器和约束实现,可以参考动物派队、人类一败涂地的角色控制,该技术还常用于实现角色受击、尸体倒地等

我使用的是插件Ragdoll Animator 2,使用起来非常简单,而且可以做角色动画和物理的混合

人物模型初始化

  1. 导入一个模型,将其Rig设为Humanoid
  2. 将模型拖入场景,添加组件Ragdoll Animator 2
  3. 点击Try Auto-Find Requied Bones,人形模型可以自动初始化

ragdoll

参数调整

  1. 调整碰撞体的类型、大小(Construct–Colliders)
  2. 调整躯干重量、摆动范围(Construct–Physics)
  3. 调整肌肉力量、动画混合程度(Motion)
  4. 设置IK(在Extra–Utility–Kinematic Bone Selector)

动画压缩

用ALU和效果换存储和带宽,动画有很多关键帧和插值函数组成,Unity Editor下可以对动画Clip进行采样,生成更简化的关键帧,在运行时通过插值还原回动画信息

评论