Challenges
可交互性和动态变化的动画:
在游戏中不能预设玩家的行为
游戏中的动画要和很多gameplay互动
受制于周围的环境
实时:
每一帧都要计算
动画数据很大
真实度:
更生动
表情
Basics of Animation Technology
2D Animation
Sprite animation
Sprite-like animation technique in pseudo-3D game:在各个视角采了一系列的位置,根据相机的位置播放不同的sprit动画
粒子系统:是一个序列帧
Live2D:
把角色的所有元素分解成一个一个的小图元,通过旋转、放缩、变形
给所有的图元设置深度
给每一个图元生成一个控制网格
设置key frame
3D Animation
- DoF(Degrees of Freedom)
6DoFs:平移的三个自由度(xyz),旋转的三个自由度(xyz)
Rigid Hierarchical Animation:层次结构刚体动画
- 问题是骨骼转的时候mesh彼此会穿插
Per-vertex Animation:顶点动画
把每个顶点的offset按时间序列存在一个texture里面
横轴是每个顶点的offset(有时也会把法线存下来)
纵轴是时间
Morph Target Animation(也是一种顶点动画)
- 在顶点之间的Key Frame去插值
3D Skinned Animation
- 蒙皮动画,每个顶点受到多根骨骼的影响,来保证在运动的时候是水密性的,不会互相穿插
2D Skinned Animation
Physics-based Animation
布娃娃系统Ragdoll
衣料模拟
Inverse Kinematics(IK)反向动力学
- 给定一个指定点,角色该怎么运动才最自然
Animation Content Creation
在编辑器里手key动画
动作捕捉
Skinned Animation Implementation
How to Animate a Mesh
Mesh:先做一个网格
Skeleton:为网格适配一套骨骼
Skinning/Rig:刷上一层蒙皮,给每一个顶点设置骨骼的权重(也就是顶点收到哪个骨骼影响大,影响小)
Pose:让骨骼按照指定的动作动画
Animation
Different Spaces
World Space
Model Space:以模型为中心的space
Local Space:局部坐标系,每一根骨骼会平移旋转,每个骨骼的坐标系是不同的,坐标系会传递
把Local坐标系从根节点,一路积分上来,才能算出model space,然后考虑模型的位置和旋转,才能再转换成世界坐标系,然后才能被渲染
Skeleton for Creature
- 人的骨骼,root一般在胯部,Pelvis,脊椎末端
Joint vs. Bone
Joint关节:会直接存储关节的数据,因为关节会带动bone动,不会直接存储bone的数据
joint是有很多自由度的
joint是一个刚体,不会被twist(扭曲),但Bone会
Humaniod Skeleton in Real Game
人脸上可能要加很多骨骼来做细节、翅膀、斗篷的骨骼
所以要提前设定好标准骨骼数是多少
Joints for Game Play
- 武器要mount(镶嵌)到手上的武器joint上
Root Joint
脚的中心,用来判断离地高度
Pelvis joint是root joint的第一个child joint
Horse Skeleton
四足动物的尾椎骨是Pelvis joint
root在肚子下面
Bind Animation for Objects
一些object是有父子关系的,有些动画是绑定的
在人的身上有一个Mount的joint,马鞍位置也有一个Mount的joint,这两个joint重合在一起,不仅是位置重合,旋转也是重合的;人和车也有
Bind Pose - T-pose vs. A-pose
- T-pose角色的肩膀部分其实是挤压的,所以目前一般就会给A-pose,这样肩胛处精度会高一点
Skeleton Pose
骨骼一个动作静止的状态叫做pose
把pose连在一起就是一个动画
在真正表达一个动画的pose的时候是有9个自由度的9DoFs,也就是放缩
Math of 3D Rotation
- 2D Orientation Math
- 3D Orientation Math
- Euler Angle:Yaw航向角、Pitch攻角、Roll横滚角
转的顺序不同,效果也不同
Gimbal Lock万向结
陀螺仪:可以用来测导弹的轨迹
稳定器
Degeneration of Euler Angle
- 当一个轴转了90度时死锁
Problems of Euler Angle
难以插值
欧拉角的叠加很难
容易沿着xyz轴转,但难以沿着其他轴转
Quaternion 四元数
- Complex Number(复数)and 2D Rotation
- Quaternion,模、共轭(虚部全部取负数)、逆(normalized的四元数的共轭就是它的逆)
在做旋转的时候,需要先乘以q转换为四元数,旋转完后再乘以q^-1
Rotation Math by Quaternion
- Given Axis Rotation by Quaternion
Joint Pose
Orientation(朝向,其实rotation就是改变Orientation)
- 大部分骨骼动画是旋转
Position:Pelvis、facial joints和一些特殊的joints才会发生位置的变化
Scale:面部变化可能会使用scale
Affine Matrix:joint表达的核心仿射矩阵,与透视矩阵不同的是不需要除以w,因为这是在一个永远正交的空间中进行变换(应该是最后光栅化的时候才会做投影)
- 关节在变换的时候,其所有父关节local space变换矩阵的乘积等于当前关节model space的变换矩阵
为什么要把所有的坐标信息存到局部坐标系呢?
如果在model space进行插值,进行的是平移变换,那么骨头的长度会发生变换
而在local space进行插值,进行的是旋转,所以此时骨头长度是正常的没有发生长度的变化
Single Jpint Skinning
每一个顶点都可以用一个权重参数被绑定在一个或多个joint上面(但最好不要超过4个,一般会用一个byte来存这个限制数量)
当关节在动的时候,绑定在这个joint的顶点也要跟着移动才对
Skinning Matrix:动画矩阵,当一个顶点绑定了某个joint后,顶点的原位置乘上这些joint的Skinning Matrix就可以获得变换后的位置
- Skinning Matrix Pallette:动画矩阵的表,有三部分,首先是模型坐标系到世界坐标系的变换矩阵,依赖的joint在模型坐标系下的最终pose,绑定的joint模型坐标系下变换矩阵的逆
Weighted Skinning with Multi-joints
- 如果绑定了多个关节,那么权重和要为1
Weighted Skinning Blend:分别算出顶点关于每个joint变换后的位置(根据上面的方法),然后根据权重插值(必须要在模型空间,不能在local space,因为每个joint的local space是不同的)
Interpolation between Poses
对于位移和scale,使用线性插值就可以
但是对于旋转不行,需要在q1和q2的插值基础上做一个Normalization,这样就能得到正确的插值,这个方法叫做NLERP;问题是这个旋转的速度是不均匀的,所以有了SLERP
Shortest Path Fixing of NLERP:最短路径的插值,因为一个位置转到另外一个位置,有两种转法,一个是直接最短路径转到,另一个是转一圈后才转到
SLERP:Uniform Rotation Interpolation
找到两个q(四元数)之间的夹角,根据当前位置的θ角进行一个插值,但缺点就是反三角函数的运算比较费(因为要查表去算)
用sinθ做分母是不稳定的
所以最终的措施是结合NLERP和SLERP
夹角小用NLERP,夹角大用SLERP
Simple Animation Runtime Pipeline
首先有大量动画的clips
根据上一帧和下一帧来插值出当前的pose
转换到model space里
计算出每个骨骼的Skinning Matrix Palatte
然后进入gpu,计算顶点位置
Animation Compression
动画数据存储数据量很大
每个人的模型有70个关节,每个关节要在每秒钟存30个pose,每个pose包含平移旋转和放缩
但是很多数据其实是不变的,比如scale,很多情况下都是1,很多位移的local position也是不变的,变的最多的是rotation,rotation中也有经常变和不经常变的
Simplest Compression - DoF Reduction
丢弃掉不变的track,比如scale track,在人形骨骼中,除了面部基本都不会变
丢弃掉translate track,在人形骨骼中,除了pelvis、facial joint和特殊的joints外也基本不变
Keyframe
keyframe extraction-Linear Keys Reduction:剔除原来pose里的非关键帧,根据插值出来的pose和实际pose来算error,只要error在阈值之下就选取作为关键帧,优点是线性插值计算简单,缺点就是为了减小error往往要打很多的关键帧
Ctamull-Rom Spline:一个三阶多项式曲线,这个曲线很平滑
- Float Quantization:浮点数32bit,存储量很大,所以可以把float的值先映射到(0,1),然后再映射到(0,65535),也就是一个16bit的无符号整型
Quaternion Quantization:如何对一个四元数进行定点压缩呢?
当对四元数归一化压缩后,虽然每一个数都有可能在[-1,1],但如果把最大值丢掉,那么另外三个数一定在 $ [-\frac{1}{\sqrt {2}},\frac{1}{\sqrt {2}}] $ ,那么就需要只用2个bit记录下哪一位是最大值,然后记录剩下的三个值,最后根据归一化反向算法(其实就是根据记录的三个值算剩下一个值),算出所有值,也就是说,每一个虚部只需要使用15bit,所以最后存一个四元数就用3个无符号整型就可以,也就是48bit;反之,如果用4个float存,那就是128bit
Error Propagation:error会传递下去,前几个error还行,当传到最后几个骨骼的时候,error就会很大了,所以压缩算法可能出现这样的问题;
Joint Sensitivity to Error:不同的joints对error的敏感度不同,比如在手的地方,压缩算法做不好就会动画抖动
Measuring Accuracy
data error
visual error:难以去计算每个顶点的visual error,在每一个joint设置两个垂直于它的点,通过设置offset的大小来控制error,因为不同骨骼对于error敏感度不同,设置的offset就可以不同
Error Compensation-In Place Correction
因为计算是传递的,所以下一个joint对上一个joint产生的error进行补偿
产生的问题就是,在末端骨骼的动画数据一般是平滑的低频数据,但是为了给上面的joint补偿而变得高频,比如产生抖动
Animation DCC Process
Mesh:先构建mesh,美术构建的是高精度mesh,但是做动画会使用low poly;在动画关节的地方会额外加几圈mesh
Skeleton binding:在现代的软件中,搭好T-pose或者A-pose后,使用工具中的骨架然后一个个对上;加上武器、pelvis、root的骨骼
Skinning:现代软件都是自动计算,根据深度学习之类前沿的算法计算出来,动画师也可以自己调整,每一根骨骼对顶点的权重是多少
Animation creation:设置关键帧和关键帧之间的时间间隔
Exporting:root不会跟动画一起存下,而是会单独导出来一个位移曲线来给引擎用