引擎动画系统设计
数据结构定义
SkeletonJoint 用来定义骨架中的一块骨骼
Skeleton 由若干 SkeletonJoint 定义的骨架
PoseJoint 定义“姿态”的骨骼
AnimPose 由多个 PoseJoint 定义骨骼姿态
AnimKeyFrame 动画片段中的一个关键帧,也是一个“姿态”,只是增加了关键帧时间
AnimClip 动画片段,由若干动画关键帧组成
运行时
AnimPipeline
运行时动画播放及混合,由 AnimPipeline 定义接口来驱动完成.
AnimPipeline 通过驱动混合树来完成动画的播放.
执行时,首先从混合树的叶子结点向父节点遍历,生成执行顺序,然后每帧按顺序执行
AnimPipeline 以混合树根节点的输出作为输出,可以后续继续处理
ParamTable 参数表
执行混合时,需要一些参数来计算混合因子
ParamTable 实现了以字符串名字为索引的参数表,可以支持整数,浮点数等简单参数类型
执行 AnimPipeline 时,向其传递参数表,使其在执行时可以查询参数.
BlendTree 混合树
混合树定义了一个复杂的动画片段混合规则,基于该规则,结合各种混合参数,输出一个确定的动作.
比如移动是一个动作,通过1D混合树,可以根据移动速度,通过混合 走,跑,快跑 这几个动画片段,输出合理的移动动作的动画.
BlendOp 混合运算符
因为我们采用类似语法树的混合树,因此也可以叫混合运算符
混合树基于两种最基础的混合行为,以及一个从动画片段提取动画姿态的运算符
二元混合
AnimPoseBinaryBlender
输入: 2个 AnimPose,及2个混合参数
输出: 混合结果 AnimPose
三元混合,或三角混合
AnimPoseTripleBlender
输入: 3个 AnimPose,及3个混合参数
输出: 混合结果 AnimPose
三个混合参数基于三角形重心坐标计算而来,因此又叫 三角 混合
动画片段播放/姿势提取器
AnimKeyFramePicker
输入: 一个 AnimClip,及播放时间
输出: 提取出 time 前后两个关键帧 AnimKeyFrame
BlendCtrl 混合流程控制器
定义混合流程控制器,来构建混合表达式,并建立输入/输出关系
IBlendCtrl 接口
定义 2-n 个输入 AnimPose*
定义 1 个输出 AnimPose
定义混合运算符类型:2元/3元
定义混合因子计算虚接口,由派生类实现
执行混合
调用虚接口计算混合因子
混合因子计算,依赖于外部输入,可以硬编码进行定制逻辑,也可以通过查询 ParamTable
根据混合类型,执行相应的混合运算
BlendCtrlClip
以 AnimKeyFramePicker 输出的两个 AnimPose,执行2元混合
BlendCtrl1D
将输入定义到数轴上,根据参数从数轴上选择两个输入,并计算混合因子,执行二元混合
BlendCtrl2D
由两个 BlendCtrl1D 输出到另一个 BlendCtrl1D 组合而成
BlendCtrl3D
3D混合是将动画映射到2D坐标架上,形成若干个三角形
播放动画时输入2D坐标,混合控制器会确定该坐标位于哪个三角形内,以及其重心坐标
用重心坐标作为混合参数,执行三元混合
AnimationStateMachine 动画状态机
基于 AnimPipeline 构建动画状态机
动画状态机负责连接逻辑层与动画层,因此上面提到的 ParamTable 可以定义在动画状态机内部
管理多个 Layer 并实现 Layer 之间的混合
AnimState 动画状态
状态机中的每个状态,通过 AnimPipeline 实现动作播放
定义接口来与其它逻辑互动
OnEnter
OnStay
OnLeave
SubAnimStateMachine
动画状态过于复杂时,可以用子状态机构建分层结构
AnimStateTransition 状态转换/过渡
定义转换规则 A->B:
转换时的播放规则:
A和B都正常播放
A暂停(froze),B正常播放
淡入淡出规则
线性
贝塞尔曲线
转换窗口:在A动画定义播放区间,在该区间内才可以启动转换
转换打断: A->B 时,播放新动画时,如何处理
A->B->A
A->B->B
A->B->C
AnimStateMachineLayer
Layer 引用一个与定义的 Avatar 资产,确定该动画状态机上的动画播放,影响哪些骨骼(链).同时定义一个Layer 间的混合因子.
Avatar 生成的骨骼掩码可以直接传递给 AnimPipeline,避免不必要的动画播放/混合.
AnimStateMachineLayer 是真正的状态机,AnimStateMachine 管理多个 Layer
Avatar
对于人形骨骼,将骨架分成四肢,头,躯干6个部分,这6个部分分别由一个骨骼链定义.基于这些骨骼链,执行一些针对部分骨骼的操作,比如上下半身融合,IK等.
蒙皮调色板
动画状态机的输出,将被用来生成矩阵,作为蒙皮调色板.由于我们的动画是基于骨骼局部空间的,同时已经去除了Tpos,因此直接从父到子生成矩阵,即得到模型空间调色板
AfterAnimation
动画播放完成后,可能要进行一些动画修正
动画重定向
离线重定向会生成新的动画片段
运行时重定向,要考虑两个动画所在的骨骼的 T-Pose 的差异,重定向时
AnimPose 中的每块骨骼,减去每块骨骼的 T-pose 差异
处理Avatar 中骨骼链差异,可能会多,或少几块骨骼,进行相应的合并/拆分操作
额外的约束.考虑不同体型差异,重定向后,可能会模型穿插,通过增加骨骼约束(固定值或,关键帧)
重定向定义在 AnimClip 上,在BlendCtrlClip逻辑后,增加重定向操作.
捏脸/身体
通过为骨骼增加修饰(长度,缩放),来改变骨架的形状.
由于这部分操作会影响骨骼长度,因此要在IK之前应用
IK
头部,脚部,手部等IK逻辑
方案一
胳膊,腿,都党委2bone关节处理:确定目标姿势下,两块骨骼构成的平面,在该平面上,计算两块骨骼构成的圆的交点,两块骨骼对齐到该交点.交点有两个,选择距离之前骨骼连接点最近的那个.或具体分析.
然后按照重定向的方式,如果实际骨骼数量多,则拆分到中间的多个骨骼上.
方案二
按照骨骼链,执行 CCD,或ForwardAndBacwardReaching IK 逻辑
挂点
挂点是世界空间的
直接使用某块骨骼
定义虚拟骨骼:以某骨骼为父骨骼,定义偏移,旋转.
同步节拍器
有时,两个不同角色,要以相同的频率播放不同动画(通常时长一致.也可以用归一化时间来同步播放,避免不一致带来的问题),比如一个人骑在马上,马奔跑动作,人要播放相应匹配的动作.
由一个角色创建节拍器,另一个引用.更新时,优先使用节拍器作为局部时间.
优化
资产优化
旋转四元数都在-1, 1 之间,由于有归一化约束,可以只保存3个分量
根据模型实际情况,如果位置精度要求不高,可以只保留2-3位小数
缩放一般不需要高精度可以映射到16位整数。大部分情况下没有缩放,可以不存储
通过存储与上一帧的差,降低精度要求
关键帧,可以用B样条拟合曲线,减少关键帧数量.效果会差一些.
运行时优化
并行优化
每个 AnimPipeline 都是无关的,可以把所有的 AnimPipeline 更新,提交到多线程任务系统中,并行处理
GPUSkinning
如果对动画质量要求不高,可以采用该方案
将每个动画片段,离线播放,将关键帧生成矩阵调色板,存储到贴图
运行时,在VS内部,根据当前播放的动画,查找到影响该定点的骨骼矩阵,计算蒙皮顶点,然后根据蒙皮骨骼权重,加权平均.
MorphTarget
基于美术制作的BaseMorph和表情Morph,用 Morph - BaseMorph,得到 MorphTarget
运行时,根据口型或其它参数,确定参与融合的 MorphTarget
将多个 MorphTarget 加权平均后,加到 BaseMorph 上
应用范围:表情,口型,肌肉,等