【动画系统】
1、精灵动画(sprite animation)是最基础的动画,主要用于实现2D动画。在3D游戏中,远离摄像机的物体也会使用到精灵动画,如远处的杂草、观众。
2、刚性层阶式动画(rigid hierarchical animation)。此法中,角色由一堆刚性部分建模而成。刚性层阶技术最大的问题在于,角色的身体会在关节位置产生碍眼的“裂缝”。
3、每顶点动画(per-vertex animation),一个蛮力技术。此法中,动画师为网格的顶点添加动画。这是一种数据密集的技术,因为每个顶点随时间改变的动作信息都要存储下来。
4、变形目标动画(morph target animation)。仅制作相对较少的固定极端姿势(extream pose)。在运行时把两个或以上的这些姿势混合,就能生成动画。变形目标技术通常用于面部动画(facial animation),因为人脸具有非常复杂的解剖结构。
5、蒙皮动画(skinned animation)。此技术含有每顶点动画及变形目标动画的优点,允许组成网格的三角形做出变形。也有刚性层阶式动画的高效性能及内存使用量特性。骨骼是由刚性的“骨头”(bone)所建构而成的。称为皮肤(skin)的加油三角形网格会绑定于骨骼上,其顶点会追踪关节(joint)的移动。蒙皮上每个顶点可绑定至多个关节,因此当关节移动时,蒙皮可以自然地拉伸。
6、相比原始的每顶点动画,大多数动画技术也可被视为一项压缩技术。
7、骨骼是由刚性的关节(joint)层阶构成。骨头一词名为副实,技术上来说,骨头只是关节之间的空位。骨骼的关节形成层阶结构,其中一个为根。骨架包含关节,关节通常依次放置在数组中,第0个元素即为根关节。
1 struct Joint 2 { 3 Matrix4x3 m_invBindPose; // 绑定姿势之逆变换 4 const char* m_name; // 人类可读的关节名字 5 U8 m_iParent; // 父索引 6 }; 7 struct Skeleton 8 { 9 U32 m_jointCount; // 关节数目 10 Joint* m_aJoint; // 关节数组 11 };
8、一个关节的姿势(pose)定义为关节相对某参考系(frame of reference)的位置、定向和绽放。通常用SQT(缩放、四元数、平移)来表示。绑定姿势(bind poase),有时候也叫参考姿势(reference pose)或放松姿势(free pose),或T姿势(T-pose)。我们有时用局部姿势(local pose)描述相对父的姿势。
9、有些游戏引擎不容许关节绽放。有些引擎会假设,若使用绽放,其必须为统一绽放,即3个维度上的绽放都相同。内存中表示关节姿势:
1 struct JointPose 2 { 3 Quaternion m_rot; // Q 4 Vector3 m_trans; // T 5 F32 m_scale; // S, 仅为统一绽放 6 }; 7 8 struct SkeletonPose 9 { 10 Skeleton* m_pSkeleton; 11 JointPose* m_aLoclPose; 12 };
10、把关节姿势表示为模型空间或世界空间会很方便。这称为全局姿势(global pose)。我们可以扩展SkeletonPose的数据结构,以包含全局姿势。
struct SkeletonPose { Skeleton* m_pSleleton; // 骨骼 + 关节数量 JointPose* m_aLocalPose; // 多个局部关节姿势 Matrix44* m_aGlobalPose; // 多个全局关节姿势 };
11、游戏角色必须拆分为小粒度的动作,这些个别的动作称为动画片段(animation clip) 。
12、动画师会在片段中指定的时间点上设定一些重要而少量的姿势,这些姿势称为关键姿势(key pose)或关键帧(key frame),然后计算机会插值计算中间的姿势。动画引擎能够在片段间任何时间内采样,不一定要在整数帧索引上采样。
13、1s动画以每秒30帧采样,其时长是30帧,并含有31个采样。若片段是非循环的,N个帧动画有N+1个独一无二的采样。若片段是循环的,那么最后一个采样是冗余的,因此N个帧的动画有N个独一无二的采样。
14、要令片段好好地循环,片段最后的角色姿势必须完全和最初的姿势匹配。那么也即意味着,循环片段的最后一个采样是冗余的。因此许多游戏引擎会略去循环片段的最后一个采样。
15、要同步两个或以上的动画片段,而它们的持续时长又不相同,归一化时间就很适用。使用局部时钟来同步多个动画时会导致多个动画协调的问题,使用全局时钟可以缓和动画同步问题。
16、每个动画片段是为特定骨骼设计的,若一组骨骼基本上是相同的,那么过于细节的骨骼的抛弃可以实现骨骼动画的复用。
1 struct Animation Sample 2 { 3 JointPose* m_aJointPose; // 关节姿势数组 4 }; 5 6 struct AnimationClip 7 { 8 Skeleton* m_pSkeleton; 9 F32 m_framesPerSecond; 10 U32 m_frameCount; 11 AnimationSample* m_aSamples; // 采样数组 12 bool m_isLooping; 13 };
17、元通道(metachannel)数据是在动画每一帧所提供的额外的数据。较常见的一种特殊通道是在多个时间眯上储存事件触发器(event trigger)。又例如,当左脚或右脚接触地面时,便可以播放一个脚步声及一个“尘雾”粒子效果。
18、把三维网格顶点联系至骨骼的过程称为蒙皮(skinning)。每个顶点可绑定一个或多个关节。若只绑定一个关节,它就会完全跟着关节移动。若绑定多个关节,该顶点的位置就等于把它逐一绑定至个别关节后的位置,再取其加权平均,每个顶点的权重因子之和为1。因所有关节权重和必须为1,所有最后一个权重可以略去。
19、通常游戏引擎会限制每个顶点能绑定的关节数目。典型的限制为每顶点4个关节。原因如下,首先4个8位关节索引能方便地包裹为一个32位字。此外,每顶点使用2、3、4个关节所产生的质量很容易区分,但多数人并不能分辨出每顶点4个关节以上的质量差别。
1 struct SkinnedVertex 2 { 3 float m_position[3]; // (Px, Py, Pz) 4 float m_normal[3]; // (Nx, Ny, Nz) 5 float m_u, m_v; // 纹理坐标(u, v) 6 U8 m_jointIndex[4]; // 关节索引 7 float m_jointWeight[3]; // 关节权重,略去最后一个 8 };
20、我们需要一个矩阵,该矩阵能把网格顶点从原来位置(绑定姿势)变换至骨骼的当前姿势。我们称此矩阵为蒙皮矩阵(skinning matrix)。蒙皮矩阵也是在模型空间定义的。我们可以把模型空间的绑定姿势位置转换至关节空间,再把关节移至当前姿势,最后把该顶点转回模型空间。蒙皮矩阵=绑定姿势美逆矩阵*当前姿势矩阵。
21、我们须计算一组蒙皮矩阵Kj,当中每个矩阵对应第j个关节。此数组称为矩阵调色板(matrix palette)。
22、每个顶点最终会由模型空间变换至世界空间。因此有些引擎会把蒙皮矩阵调色版预先乘以物体的模型至世界变换。这是个很有用的优化,因为渲染引擎渲染蒙皮几何时,每个顶点能节省一个矩阵乘法。
23、对多单顶点绑定多关节的情况,我们可以计算顶点分别蒙皮至每个关节,产生对于每个节点的模型空间位置,然后把这些结果进行加权平均来求出最终位置。