动画技术的基础
2D游戏动画技术
Sprite animation:通过多张图片连续播放获得的近似动画效果,早年红白机小霸王上的游戏大多此原理 Sprite-like animation:在前者基础上利用多个视角相机进行拍摄获得的更全面的图片信息,从而可以实现模拟3D的效果(Doom) Lived2D:指二次元人物的动画等,一般利用将人物拆分成不同组件并通过移动旋转组件来实现人物动画
3D游戏动画技术
基础概念DoF(Degree of Freedom),即指这个系统能操作的方式。比如常见的6DoFs就是指物体可以在xyz轴运动以及绕xyz轴旋转,如下图:
Rigid Hierarchical Animation
最基础的3D角色动画,就是有层次的(如树状)的刚体的运动,形式类似皮影戏和上面的Live2D。这种方法的问题是刚体间移动旋转时可能会出现缺缝。
Per-vertex Animation 顶点动画
如旗帜、水流的动画效果用刚体难以解决,所以使用3DoFs的顶点动画,计算每一帧下每个顶点的空间位置的变化,一般存成两个texture,第一张的x轴是所有顶点的序号,y轴是帧的数量,位置(x, y)则是某顶点在某帧下空间位置变化的offset;第二张图是记录顶点的法向量,因为随着位置的变化,法向也会变化。这两张图都会利用物理引擎先预计算保存。 缺点是数据量会很大。
Morph Target Animation
也是顶点动画的一种,但是只设置关键帧的变化,关键帧之间普通帧的变化是通过插值获得。
3D Skinned Animation 蒙皮动画
在骨骼外有皮肤(mesh),每个mesh的顶点受多个骨骼节点的影响(加权)。 其优点是减少了顶点动画的数据量,以及mesh会更自然。 同理还有2D的蒙皮动画。
Physics-based Animation
基于物理的动画,常见于三个方面:Ragdoll,即人从高空坠地时的一些动画,往往部分由艺术家设计实现,部分还需考虑物理原理(自由落体以及坠地瞬间);衣料的动画;Inverse Kinematics(IK)反向运动学,比如攀爬时如何让这个抓取过程更合理。
动画内容的创造
主要有两种方式:
- DCC(Digital Content Creator)+ Animator。即所谓的艺术家手K动画(key frame)
- 动捕(Motion Capture)
蒙皮动画的实现
流程上分为五步,一般在vertex shader中实现: 看似简单,但如果顶点的权重设置不合理,模型一动起来可能就炸了。
【前提知识】局部坐标系。动画中的局部坐标系是以骨骼的单个部分为单位的,而不是整个人为局部坐标系,所以在计算中往往需要依次传递到具体的骨骼的局部坐标系(比如手的局部坐标系),反过来说也需要从局部坐标系传回世界坐标系才能进行渲染。
生物骨骼
生物骨骼是由一个个有层次的节点组成,其中有一个是根节点,其他所有节点都有父亲节点。 对人而言,一般取脊椎的最后一个骨头为root节点,因为从这个节点开始向下是双腿,向上是脊椎,决定人上体的曲直,继而还有肩膀双臂和头颈。 动物会有不同,因为是四肢动物。 另外,Joint节点和Bone骨骼不完全相同,一般来说,我们把关节的地方称为节点(有自由度),节点间称为骨骼。
实际游戏中,往往取人双腿之间的点为root节点,这样方便后续计算速度以及跳跃高度等数据。而马这种四肢动物则会令肚子上的节点为root节点。
不同物体之间需要绑定,比如骑马时马和人各自拥有不同的动画,所以需要设置一个bind point,使得两者能够被绑定在一起,而且该点具有比较高的自由度,能够实现当马前倾时人也会前倾等行为,而不只是一个纯连接点。 在人体连接方面,实践中也逐渐抛弃之前常用的T型结构而改用A型。这主要是因为T型结构在肩膀部分会挤压带来精度误差。
3D旋转的数学原理
Euler Angle
旋转主要通过欧拉角的概念,如下图: 可以通过绕xyz旋转的矩阵相乘来获取最终效果。 另一种表达欧拉角的方式是Yaw, Roll和Pitch,分别指水平的旋转、左右侧向高低变化以及抬头低头的变化。
欧拉角的主要问题有
- 顺序依赖,即换一种绕不同轴旋转的顺序会导致最终旋转结果不同。所以一般规定必须xyz顺序。
- 欧拉角存在“退化”问题,即当固定绕一个轴旋转时,其他两个轴就退化成同一个内容了,比如绕z轴转动时,x和y的地位就相同了,此时DoF实际上只有1度了。
- 插值困难。给定两组旋转角度时,很难计算它们之间的插值结果,不能简单线性插值。
- 很难绕其他任意特定轴旋转。
Quaternion 四元数
出于上述缺点,实际游戏制作中不太用欧拉角,反而用Quaternion会比较多。 其基于利用复数解决二维旋转问题的方法,如下图: 进一步提升到三维空间后: 比较值得注意的是
i
2
=
j
2
=
k
2
=
i
j
k
=
?
1
i^2 = j^2 = k^2 = i j k = -1
i2=j2=k2=ijk=?1这个式子,从中可以得出 ij = k, ik = j, jk = i 的事实。从而便于理解后面两个旋转的累积结果的矩阵。 与二维的复数一样,这个四元数也有共轭和互逆的概念。
欧拉角到四元数的转化公式: 四元数的旋转: 其中q是指旋转的四元数,v’ 是v进行q旋转后的结果 对上式进行计算优化后,结果可以表示为: 在以上基础上,利用四元数可以表示很多三维空间的旋转,包括反旋转、旋转的结合以及从当前方向u到指定方向v的旋转函数计算等:
四元数还有一个应用就是围绕任意固定轴旋转,如下图: 其中u就是那个指定轴的单位坐标,q就是与这个轴和旋转角度相关的一个旋转四元数,v’即v旋转后的结果。
蒙皮动画的细节:关节与蒙皮
关节的Pose有三个维度:Orientation、Position和Scale,合并起来形成了Affine Matrix: 结合上面所说的坐标系的关系,当前关节的pose映射到世界坐标系需要不断转换,如下图: 那为什么要用局部坐标系而不是直接所有关节都用模型整体的坐标系呢?除了要转换的数据量更少以外,还因为模型坐标系下容易出现插值错误(旋转过程中的插值会直接从起点到终点的连线上取,从而出错)
在第一个局部坐标系下:(前提是这个mesh顶点和节点J是绑定的,即局部内不动) 结合上式,可得: 也就得到了mesh点最初在模型坐标系下的坐标到 t 时间时的模型坐标的变换矩阵Skinning Matrix K。
实际游戏引擎中会先计算好一个SkInning Matrix Palette表,并计算好各个mesh点原始坐标,从而可以快速利用GPU在shader中计算出当前时刻 t 的mesh坐标。 另外,实际中Skinning Matrix K还会再在左边乘上世界坐标系的转换矩阵
M
w
M^w
Mw 完成到世界坐标系的转换。 当然,这一切都在上述绑定的前提下。
我们一般以以下形式存储节点: 其中逆矩阵是预先计算好从而避免运行时计算浪费时间。
而实际引擎中mesh上的顶点会采取加权方式考虑多个节点的影响(理论上不设上限但一般不超过4个节点),具体来说,先将这个mesh顶点相关的多个节点分别转换到模型坐标系,然后对它们加权求和即可。 之所以不直接对局部坐标系加权求和是因为不同局部坐标系完全无法统一处理。
我们将模型多帧的pose称为一个clip,一般是十几二十帧,远小于实际游戏帧率,所以还是需要插值:
- 针对位移,一般直接线性插值即可 – LERP
- 针对旋转,先取线性插值结果,再对其做normalization即可,这样也是避免长度发生变化 – NLERP
插值中值得注意的是,当两个变换q1和q2相乘结果小于0时,可能会出现错误,例如本来只需反向转30度,现在正向转了330度,从而导致插值结果从 -30~0 变为0 ~ 330。所以此时的插值公式会发生一些变化,如下: NLERP仍然存在一个问题,那就是插值不均匀,因为是按两点连线均匀划分的,所以会导致两边时间快,中间时间慢的问题(因为中间的圆弧对应的线段长),所以又提出了按角度进行插值的SLERP: 但这种做法在角度小的时候因为sinθ过小所以出现问题,且相对计算较复杂(需要查表),所以实际中一般会看情况使用。当角度较大时使用SLERP,较小时直接用NLERP。其总结如下:
Pipeline
动画压缩技术
简单压缩
如果以上面所讲的方式存下所有节点每秒比如三十帧的数据,会导致数据量过大没法使用,所以必须进行压缩。 第一步是进行DoF的压缩,我们可以发现,对大部分节点来说,其scale不怎么变化,所以可以直接去掉(除了脸部节点);同时,节点的translate也不怎么动,只需要存储固定值(除了骨盆和脸部节点)。
旋转
而针对旋转,比较简单的方法是使用关键帧Key Frame进行插值,当插值误差小于一定阈值时就设为关键帧,不然就把这插值对应的GT帧加入关键帧再继续细分。因而关键帧的间隔不是固定的。
Catmull-Rom Spline
关键帧的结果虽然不错,但仍然是一条折线。所以又提出了Catmull-Rom Spline: 在两帧P1P2之外分别再取P0P3,然后拟合这个曲线即可。
数位压缩
首先,很多时候不需要32位浮点数精度,所以可以将数值压缩到0-1之间然后乘以65535用16位整数去表示。 具体的,针对四位数Quaternion,利用其特性(四元数的四个值中有一个较大,其他三个必然在
?
1
/
(
2
)
-1/\sqrt(2)
?1/(
?2) 到
1
/
(
2
)
1/\sqrt(2)
1/(
?2)之间,同时因为normalization,所以可以用其他三个较小的值去表示较大值,因而实际可以这样存储: 综上所述:
Error
压缩虽然好用,但会带来误差累积问题。所以需要检测误差是不是被控制在可接受范围内。 最实际可用的误差是visual error视觉误差,但如果对比压缩前后每顶点的误差则计算量太大,所以实际中针对每一个节点joint进行估计: 对节点估计两个互相垂直方向上的点,距离设置为offset(如果精度敏感则offset大一点,如果不敏感或小骨骼的话就offset小一点),这样只需要比较两个点在压缩前后的error即可
Error Compensation:在多帧误差累积后反向进行偏移来补偿。其问题就是这一帧和上一帧会很不连贯。
动画制作流程
整体上,the Animation DCC(Digital Content Creating) process 分为: ? Mesh(在某些pose下会再新增一些mesh来优化) ? Skeleton binding ? Skinning ? Animation creation ? Exporting
|