![]() |
![]() |
![]() |
Skeletal Animation #1 Feb 01, 2006
With skeletal animation each character is a hierarchy of bones( ie. skeleton ), and the bones control the deformation of the character mesh. What follows is an quick overview of one way to handle skeletal animation, based on a much simplified version of how I do it in my own program. As usual, my advice for anyone just starting is to get something simple working and build from there, and make sure your program can display enough visuals&values that you can tell exactly what's going on if you need to debug.
When I initially set up skeletons in my program I had joints (which controled the rotations between bones) and bones as separate types, each able to have multiple children of the other type. While there's not really anything wrong with this, I found there wasn't any need for it either. Here what I called a joint is included in my definition of bone. Structure
Off the top of my head, the basic bone structure for a skeleton might look like this:
struct Bone { quat qRotate; CVec3 vOffset; float fHingeAngle, fMin, fMax; float fLength; int nJointType; int Child[ MAX_BONE_CHILDREN ]; int Parent; char sName[ 32 ]; };qRotate - a quaternion representing the rotation of the bone in relation to its parent vOffset - a 3D vector for how to offset the bone from the end of its parent ( defaults to 0,0,0 ) fHingeAngle, fMin, fMax - Hinge angle with a minimum and maximum. These values are only needed for Inverse Kinematics. I will ignore it in this article. fLength - The length of the bone nJointType - The joint type ( normal, hinge, fixed... ) Child - The numbers in the skeleton of any child bones. (initialized to invalid) Parent - The number of the parent bone. Not strictly neccessary, but you'll probably end up wanting it. sName - The name of this bone. In some situations not needed, but definitely is for my editor. Animation
You'll want a separate structure for Bone keyframes. At their simplest, a keyframe need only include a quaternion for rotation and its frame number. I also include location and velocity (for IK & offset control,) as well as a few other elements. For the actual animating, we start with setting the proper values for the current frame by interpolating between the left and right keyframe. Spherical Linear Interpolation (Slerp) can be used for quaternions, and a linear average or hermite curves for the location vector. Once we have set the values of current keyframe, we use this keyframe to calculate a matrix for each bone. This matrix could be added to the Bone Structure above, but I can have multiple objects with separate animations that are instances of a single skeleton, so I prefer to keep it in an array of size numBones that is part of each skeleton object. The bone matrices are computed by starting with main bone and recursively processing each child bone in the same manner. For each bone we send in the current matrix, add to it the bone's rotation, store as BoneMatrix for that bone, then translate by length. Rendering
Once we have a matrix for each bone, we can draw some visual representation of the skeleton structure in its current position. On the simple end you can loop through each bone, load the bone matrix, and draw a box( perhaps of size bone[nBone].fLength,1,1 ) representing the bone. On the more complex end, you can use the skeleton to deform a mesh. The details of that will have to wait to a later article. Here's an example of how you'd do it if each vert is only attached to one bone. The InvBindMatrix of a bone is the inverse of that bone's matrix in the position the skeleton was bound to the mesh. Therefore if a bone's current matrix is the same as its bind matrix, meaning it hasn't moved at all, InvBindMatrix * BoneMatrix will cancel out as we want. CMatrix MBone[ MAX_BONES ]; for (int nBone = 0; nBone < nTotalBones; nBone++) { MBone[nBone] = InvBindMatrices[ nBone ] * BoneMatrices[ nBone ]; } // Loop through verts using just a single attached bone // (for verts attached to multiple bones we would compute the location // given by each bone for the vert and use the weighted average.) for (int i = 0; i < numVerts; i++) { pRenderVertices[ i ] = MBone[ pnBoneAttached[ i ] ] * pInVerts[ i ]; } |