Animation Blending

Started by
170 comments, last by haegarr 15 years, 5 months ago
Hello, What is Animation Blending and how to implement it? Im using Bone System. my Keyframe class: struct Keyframe { float time; CQuat qRotate; CVector3 vPos; }; my Animation class class Animation { public: Bone *root; float startTime, endTime; char *animation_name; void Animate(); //Not written yet. Will write it after my question. }; Thanks, Kasya
Advertisement
Imagine a character that is running. Now you want this charactet to stop. After he stops, you'll propably play an idle animation or something. But if you just play the idle animation when he is not running, it will look jerky.

Blending involves the part, where you blend varous (propably 2) animations together in order to achieve seemless transitions between several animations.

If you want to blend 2 animations, you can do this:
-Before you stop the running animation, start the idle animation
-You now neither use the running skeleton, nor the idle skeleton to transform the vertices of the character. Instead you create a third skeleton.
-You now calculate a blend factor for each frame:
float blend_factor = current_time/blending_time;//blending_time is the time you want the animations are blended, propably 1 second//current_time is the time, that ellapsed after you have startet the second animation, the idle for example


Now you look through each bone and do the following:
final_skeleton[ bone ].pos = running_skeleton[ bone ].pos * blend_factor + idle_skeleton[ bone ].pos * (1-blend_factor);// You will do the same for the rotation of the bone.


Now you can use this temporary skeleton to transform the vertices of the character. You will do this until blend_factor reaches 1, after that you can start playing the idle animation.


This is a very basic and rudimentary blending system and I'm shure there are alot of others and better ones out there.
I still hope that this helps :)
Thanks for your post. But i have a question.
you wrote
-You now neither use the running skeleton, nor the idle skeleton to transform the vertices of the character. Instead you create a third skeleton.

do you mean running animation, idle animation, third animation?


void Blend(Animation *a, Animation *b, float blendtime) {

float blendfactor = blendtime / b.currentTime;

Animation *final.bone->pos = a->bone.pos * blend_factor + b->bone.pos * (1-blend_factor);

}

Is that right?
Not skeletons/bones are blended, but their transformations. You compute the joint transformations of the current pose of N animations in parallel, e.g. by key-frame interpolations. Then you blend the N transformations to yield in the definite pose. That pose is then used during skinning.

For the blending it plays no role which animations are blended. And you get "virtual" animations, i.e. animations that are not specially made by an artist (and hence not saved as an instance).
Then that means i need to make attachning mesh, skinning and after all animating a bone?
We need to clarify what running means.

Suppose you've got a skeleton for every keyframe in an animation and you want to create a skeleton that fits in bewteen those keyframes, you blend those 2 keyframe skeletons and store the result in a skeleton that is used to transform the vertices:

class CAnimation{    UINT number_of_frames; // Those are the number of keyframes in your animation    float length; // This is the length of the animation in seconds    float time; // This is the time that is elapsed from the beginning of the first frame    CSkeleton m_Frames[ number_of_frames ];};void CAnimation::Blend( CSkeleton *pFinalSkeleton, CSkeleton *pLastSkeleton, CSkeleton *pNextSkeleton, float blend_factor ){    for( UINT i = 0; i < plastskeleton->num_bones(); i++ )    {        pFinalSkeleton.pos = pLastSkeleton.pos * (<span class="cpp-number">1</span>-blend_factor) + pNextSkeleton.pos * blend_factor;<br>        <span class="cpp-comment">// same for the quaternion</span><br>    }<br>}<br><br><span class="cpp-keyword">void</span> CAnimation::Animate( CSkeleton *pFinalSkeleton )<br>{<br>    <span class="cpp-comment">// compute the blend factor and the index of the two frames, being used</span><br>    Blend( pFinalSkeleton, &amp;m_Frames[ last_frame ], &amp;m_Frames[ next_frame ], blend_factor );<br>}<br><br><br></pre></div><!–ENDSCRIPT–><br><br>This is how an animation class will propably look like. If your character is running all the time, you character class (or whatever you name it) is &#111;nly going to animate this &#111;ne. But if the character is about to stand still, you will animate both animations<br><br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><span class="cpp-keyword">class</span> CCharacter<br>{<br>    CSkeleton m_FinalSkeleton;<br><br>    CSkeleton m_TempSkeleton1;<br>    CSkeleton m_TempSkeleton2;<br><br>    CAnimation m_Animations[ number_of_anims ];<br>};<br><br><span class="cpp-keyword">void</span> CCharacter::Animate()<br>{<br>    <span class="cpp-comment">// In general, it will work NOT like this</span><br>    <span class="cpp-comment">// Your class will detect that you want to move on to another animation and will just switch to this code if it is that way</span><br>    <span class="cpp-keyword">if</span>( running &amp; wanting_to_stop )<br>    {<br>        m_Animation[ RUNNING ].animate( &amp;m_TempSkeleton1 );<br>        m_Animation[ IDLE ].animate( &amp;m_TempSkeleton2 );<br><br>        <span class="cpp-comment">// Here we can blend those two skeletons like in the animation class</span><br>        Blend( &amp;m_FinalSkeleton, m_TempSkeleton1, m_TempSkeleton2 );<br>    } <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span>( running &amp;&amp; !wanting_to_stop ) {<br>        m_Animation[ RUNNING ].animate( &amp;m_FinalSkeleton );<br>    }<br><br>    TransformMesh( &amp;m_FinalSkeleton );<br>}<br><br><br></pre></div><!–ENDSCRIPT–><br><br>That's a pretty rough sketch of how it could be done. But keep in mind that this is just some scratched code. You should really use std::string instead of char * and std::vector instead of an c-array. They really rock thebig1111!!!!! :)<br><br>*edit* I used those two animations (running, idle) &#111;nly for demonstration purposes, to make things clear. In the end, the character class won't care about what bone transformations are blended, it will just do so.
Quote:Original post by Kasya
Then that means i need to make attachning mesh, skinning and after all animating a bone?

Character animation (with blending) is roughly as follows:

You have e.g. a key-frame animation for each animated parameter of the skeleton. Animated parameters are orientations and perhaps positions of what is called the bones. For the given moment of time, you have the virtual playhead of the animation anywhere between 2 key-frames for each parameter. So you can compute the current orientations/positions by using interpolation.

What you've done so far is to compute the pose on the basis of 1 animation. Now you can do the same for another animation, for a 3rd animation, and so on. So you got N sets of current parameters, where each set defines a pose from a particular animation. Then you blend the parameter sets so that parameter_a of set 1 and parameter_a of set 2 and parameter_a of set 3 ... yield in parameter_a of the resulting set; the same for parameter_b and so on.

At this point you get a single parameter set, and hence there is no longer a difference in processing in comparison of using only a single animation: With the resulting parameter set and the weighting (with which vertices are attached to bones) you do the skinning.
Thanks very very much. I won't have problem after that understanding. :)
Hello. I started writing that Animation Blending:

CAnimation::CAnimation() {

m_Frames.clear();
currentFrame = 0;

strset(name, 0);

}

CAnimation::~CAnimation() {

m_Frames.clear();
currentFrame = 0;
strset(name, 0);

}

void CAnimation::Blend(CSkeleton *pFinalSkel, CSkeleton *pLastSkel, CSkeleton *pNextSkel, float blendFactor) {

for(unsigned int i = 0; i < pLastSkel->NumBones(); i++) {

pFinalSkel->bones->vPos = pLastSkel->bones->vPos * (1.0f - blendFactor) + pNextSkel->bones->vPos * blendFactor;
pFinalSkel->bones->qRotate = pLastSkel->bones->qRotate * (1.0f - blendFactor) + pNextSkel->bones->qRotate * blendFactor;

}

}

void CAnimation::Update() {

float endTime = time + length;
float curTime = m_Timer.GetSeconds();

if(curTime < endTime) {
if(currentFrame < m_Frames.size() - 1) {

currentFrame++;

} else {

currentFrame = 0;

}
}



}

void CAnimation::Animate(CSkeleton *pFinalSkel) {

Blend(pFinalSkel, m_Frames.back(), m_Frames[currentFrame+1], 1.0f);

}

void CAnimation::AddFrame(CSkeleton *skel) {

m_Frames.push_back(skel);

}

now it doesn't show anything. I think the problem is in the Animate Function. But i can't write it. [nextFrame] must be [currentFrame + 1] or something else. And please look at my (Update) Function. Is that right?

Thanks,
Kasya

P.S. I have a CSkeleton::Draw() Function it doesn't show it here. But if i don't call CAnimtion::Animate(FinalBone), it shows the bones
Please wrap code snippets by [ source ] and [ /source ] tags (but without the spaces inside the brackets. It makes code much more readable, and hence helps us in helping you. Please edit yourt previous post accordingly, too.

The 1st problem I see in the blending of qRotate: I assume your rotations being represented by quaternions. You need to re-normalize the belnded quaternion, or else it would no longer represent a pure rotation!

Another problem is that you interpolate between the previously computed skeleton and the current pose (if I interpret your code correctly). That is not the way one would normally go.

An animation of, say, a position is given by a sequence of key-frames. For each key-frame you know the (relative) moment of time, and the position value. E.g.
{ { vPos1, 0.0 }, { vPos2, 0.5 }, { vPos3, 1.2 }, ... }
denotes that at t=0s the position should be vPos1, at t=0.5s it should be vPos2, and so on. Here t is the moment of time related to the beginning of the animation playback.

Now, when rendering the next video frame, you have the current time t', and you can relate it to the playing animation by subtracting the moment T when the animation was started:
t := t' - T
With this t, you look in to the sequence of key-frames. Let's say t==0.3s. Then the virtual playhead is between the first and second key-frames. Fetch the moments of those both key-frames, in this case
leftT := 0.0
rightT := 0.5
and make the current time relative to this period
b := ( t - leftT ) / ( rightT - leftT )
Use this factor for interpolation of the both belonging positions, i.e.
vPos := vPos1 * ( 1 - b ) + vPos2 * b
Notice that ( 1 - b ) is used for the "left" position.

The factor to be used when _blending_ is not (directly) time dependent. It depends on time only if something like fading is done. Moreover, since blending more than 2 animations should be possible, it is furthur not principally correct to blend with b and 1-b, or else weights will be weighted by subsequent blendings.

This topic is closed to new replies.

Advertisement