很久没写技术文章,今天写一下骨骼动画的过渡,也当是复习一下。骨骼动画的过渡在游戏中很常见,由一个动作切换到另一个动作有个过渡时间,这个过程其实是动作混合的过程。骨骼动画的原理不多说了,网上很多相关文章。下面先简单介绍一下骨骼动画的实现。
实现骨骼动画,我定义如下几个类:
class AnimationState,表示一个模型的动画状态,记录了动画的时间,权值等。
class SkeletonAnimation,骨骼动画,记录了所有骨的关键帧位置和骨骼关系。
class Bone,表示模型中的具体一条骨,记录了骨的位置,旋转和缩放。
class Model,表示一个模型,包括所有顶点数据。
骨骼动画的流程大概如下:
AnimationState:Update(),更新动画时间。
SkeletonAnimation:ApplyToNode(float timePos, Bone* pBone, float weight),计算每条骨在当前时间的位置。
Bone:GetTransform(),获得骨的矩阵,对受其影响的顶点进行蒙皮。
当然实现起来没这么简单,具体可以参考
ogre,基本上也是这个流程。
知道了这个流程后,其实动作过渡就很简单了。关键是SkeletonAnimation:ApplyToNode这个函数,利用参数weight的变化就可以实现了。原理就是当前动作的weight是1.0->0.0,目标动作的weight是0.0->1.0。
下面是这个过程实现的伪代码。
SkeletonAnimation animCur; //当前动画
SkeletonAnimation animDest; //目标动画
AnimationState stateCur; //当前动画状态
AnimationState stateDest; //目标动画状态
float blendTime = 0.5; //混合时间,这里假设是0.5秒
float blendRemain = 0.5; //剩余时间
Bone bones[BONES_NUM]; //骨骼数组
function Blend(float timeElapse)
{
blendRemain -= timeElapse;
bool bBlending = blendRemain >= 0;
if (bBlending)
{
float fSrcWeight = blendRemain / blendTime;
stateCur.SetWeight(fSrcWeight);
stateDest.SetWeight(1 - fSrcWeight);
}
}
function Caculatebones(state)
{
for (int i = 0; i < BONES_NUM; ++i)
{
state.GetAnimation().ApplyToNode(state.GetTimePos(), bones[i], state.GetWeight());
}
}
CaculateBones(stateCur);
CaculateBones(stateDest);
基本上这样就完成了过渡混合,其实动作混合也是这样做,如果有N个动画同时混合,把上面的代码改成如下:
for (int i = 0; i < N; ++i)
{
AnimationState state = CurrentStates[i];
Caculatebones(state);
}
这样就完成了N个动画的同时混合。
详细实现可以参考OGRE。