Sparrow WebGL Devlog 16: Animation Blending
Multiple animations and smooth animation changes
17.01.2024 - 14:06Most video game characters have more than one animation: Walking, running, jumping, attacking, blocking, climbing, swimming, and more. But how do you import and store the different animations and most importantly, how do you switch between them?
The animation system is one of the most complicated aspects of any game engine. There is no universal approach that’s the best for all scenarios. The simplest approach would be to have one animation per model and when you want to change the animation, you just replace the whole model. On the other hand of the spectrum, you can have multiple animations for the same model and very advanced inverse kinematic rigs where the environment can control the animation (e.g. always placing the feet on uneven ground).
For now (and luckily), I don’t need fully environment-driven inverse kinematics for my WebGL engine, but the character for my new portfolio website needed to have at least two animations. This meant that I had to improve my old animation system which only supported a single animation.
Multiple Animations
The first thing I had to figure out was how to create multiple animations for the same model in Blender and how to export them to a glTF file. In Blender, you can switch to the Dope Sheet and go to the Action Editor sub-panel. There you can create multiple actions and animate them. The actions will be exported as separate animations in the glTF file. I’m not sure whether this is the best way to do this, but it works for my use case.
Even though I knew it wouldn’t work, I tried to load the glTF file and promptly encountered this lovely section of code I wrote a few months earlier:
if ( animations.length > 1 )
{
console.log( "GLTF.Parser > gltf file has more than one animation, I don't know how I want to handle this yet, #TODO later" );
}
Well, later was now, and I had to come up with a solution for multiple animations. One of the reasons why I struggled with this previously is the way that glTF handles animations. In a glTF file, all animations are the same. Skeletal animations for vertex-skinned models are no different from a rotating cube. While this approach is very versatile, it’s also very generalized and doesn’t necessarily align with how I like to handle animations.
There might be cases where the same animation is applied to different models, but I prefer to have the animations directly associated with their model. I also tend to export animated models into their own files because they are usually separate game objects. For this reason, I decided to focus on the way I typically use glTF files with animated models and parse them in a way that’s logical for me.
The Sparrow.AnimatedModel class now has a list of SkeletonAnimations which is a child class of a more general Animation class. Similar to a glTF animation, a SkeletonAnimation has multiple channels for every bone and every transformation (i.e. translation, rotation, and scale). The channels update the transforms of their target bone and multiply them into the bone’s transformation matrix.
Animation Blending
As mentioned above, an animated model could have multiple animations. One of the animations was active and you could change the active animation at any time. However, changing an animation instantly can lead to some very awkward glitches. Imagine a jumping jack animation and an idle position with the arms of the character at its sides: When you switch from the jumping jack to the idle stance at a time when the arms are over the head of the character, the arms would get teleported to their resting position instantly.
Instantly switching animation looks weird:
As you can see, instantly switching between animations can look very jarring. We as humans cannot teleport our arms instantly from one position to another, so movement like this catches our eye. Instead, we have to smoothly move our arms to the correct location. This is exactly what animation blending is: Over a short time two animations are updated and the weight slowly shifts from the first one to the second, which creates a smooth transition. Luckily, this wasn’t very difficult to implement because it’s just linear interpolation (or spherical linear interpolation for quaternions), which is needed for skeletal animations anyway.
fromChannel.target.translation = vec3.lerp( fromChannel.translation , toChannel.translation , this.blendingTime/this.maxBlendingTime );
Blending between two different animations:
Blending between multiple animations is a very useful feature for a game engine or any 3D graphics framework in general. My implementation is still very basic and there are a lot more animation-related features you could add.
This has been one of the last features I needed for the portfolio website I’ve been working on. I already implemented it a few weeks ago and added it to the website. However, I’m still struggling to write all of the text required for the website. Turns out, programming is much easier than trying to write an about me section or attempting to summarize complicated projects in a few paragraphs.
by Christian - 17.01.2024 - 14:06
Comments
Comments are disabled