I wish someone had just posted code like this when I first
learned animation, some 8-10 years ago. (Has it been that long?)
Assuming OpenGL conventions (column vertices on the right,
matrices multiply from right to left in operation order).
Note: The data needed for skinned animation includes the
actual mesh with bone indices/weights, as well as the animation data
with bone position/rotation per frame. Typically, you'll get this from a
DCC application like 3ds Max, Maya, or perhaps MilkShape.
// Character soft skinning with animation blending, by Jon Watte.
// Free software under the MIT license.
// Assumes some existing utility/math functions and data types.
// Vector3: x,y,z
// Quaternion: x,y,z,w
// Matrix: 16 elements, translation on right, column major
// Matrix::translation(), Matrix::rotation() and normalize(Quaternion)
// definitions
struct Vertex {
Vector3 pos; // in object space
Vector3 normal; // should be normalized
Vector2 texCoord;
unsigned byte indices[4]; // < numBones
float weights[4]; // should sum up to 1
};
struct Bone { // assuming bone positions relative to identity pose in world space
Vector3 offset;
Quaternion ori;
};
Vertex *iVertices, *oVertices;
int numVertices;
Matrix *boneMatrix;
Bone **animationFrames;
int numBones;
GLuint buffer;
// set-up
// load iVertices and animationFrames, numVertices and numBones
...
// allocate oVertices and boneMatrix
...
// allocate the vertex buffer object
glGenBuffers(1, &buffer);
glBindBuffer(GL_ELEMENT_ARRAY, buffer);
glBufferData(GL_ELEMENT_ARRAY, 0, 0, GL_DYNAMIC_DRAW);
// to render, based on frames A and B
// derive the pose from keyframe A and B (lerp by factor L)
for (int i = 0; i < numBones; ++i) {
boneMatrix[i] =
Matrix::translation(animationFrames[A][i].offset * (1-L)
+ animationFrames[B][i].offset * L)
*
Matrix::rotation(normalize(
animationFrames[A][i].ori * (1-L)
+ animationFrames[B][i].ori * L));
}
// calculate the vertex position and normal based on 4-bone blending
for (int i = 0; i < numVertices; ++i) {
oVertices[i] = iVertices[i];
oVertices[i].pos =
iVertices[i].pos * boneMatrix[iVertices[i].indices[0]] * iVertices[i].weights[0]
+ iVertices[i].pos * boneMatrix[iVertices[i].indices[1]] * iVertices[i].weights[1]
+ iVertices[i].pos * boneMatrix[iVertices[i].indices[2]] * iVertices[i].weights[2]
+ iVertices[i].pos * boneMatrix[iVertices[i].indices[3]] * iVertices[i].weights[3];
// this only works if there is no non-uniform scale in the bones
oVertices[i].normal =
iVertices[i].normal * boneMatrix[iVertices[i].indices[0]] * iVertices[i].weights[0]
+ iVertices[i].normal * boneMatrix[iVertices[i].indices[1]] * iVertices[i].weights[1]
+ iVertices[i].normal * boneMatrix[iVertices[i].indices[2]] * iVertices[i].weights[2]
+ iVertices[i].normal * boneMatrix[iVertices[i].indices[3]] * iVertices[i].weights[3];
}
glBindBuffer(GL_ELEMENT_ARRAY, buffer);
glBufferSubData(GL_ELEMENT_ARRAY, 0, sizeof(oVertices[0])*numVertices, oVertices);
glVertexPointer(3, GL_FLOAT, sizeof(oVertices[0]), 0);
glNormalPointer(GL_FLOAT, sizeof(oVertices[0]), 12);
glTexCoordPointer(2, GL_FLOAT, sizeof(oVertices[0]), 24);
glDrawRangeElements( /* whatever your triangle list is */ );