OpenGL Skeleton

I am trying to add animation to my program.

I have a human model created in Blender with skeletal animation, and I can skip keyframes to see how the model goes.

Now I exported the model to XML format (Ogre3D), and in this XML file I see how rotation, translation, and scale are assigned to each bone at a specific time (t = 0.00000, t = 0,00040, ..., etc. d.)

What I did was find which vertices are assigned to each bone. Now I assume that all I have to do is apply the bone-specific transforms to each of these vertices. Is this the right approach?

In my OpenGL function draw () (rough pseudocode):

for (Bone b : bones){ gl.glLoadIdentity(); List<Vertex> v= b.getVertices(); rotation = b.getRotation(); translation = b.getTranslation(); scale = b.getScale(); gl.glTranslatef(translation); gl.glRotatef(rotation); gl.glScalef(scale); gl.glDrawElements(v); } 
+7
opengl skeletal-animation
source share
2 answers

Vertices are usually affected by more than one bone - it looks like you're after linear skinning. My C ++ code is unfortunately, but hopefully this will give you an idea:

 void Submesh::skin(const Skeleton_CPtr& skeleton) { /* Linear Blend Skinning Algorithm: P = (\sum_i w_i * M_i * M_{0,i}^{-1}) * P_0 / (sum i w_i) Each M_{0,i}^{-1} matrix gets P_0 (the rest vertex) into its corresponding bone coordinate frame. We construct matrices M_n * M_{0,n}^-1 for each n in advance to avoid repeating calculations. I refer to these in the code as the 'skinning matrices'. */ BoneHierarchy_CPtr boneHierarchy = skeleton->bone_hierarchy(); ConfiguredPose_CPtr pose = skeleton->get_pose(); int boneCount = boneHierarchy->bone_count(); // Construct the skinning matrices. std::vector<RBTMatrix_CPtr> skinningMatrices(boneCount); for(int i=0; i<boneCount; ++i) { skinningMatrices[i] = pose->bones(i)->absolute_matrix() * skeleton->to_bone_matrix(i); } // Build the vertex array. RBTMatrix_Ptr m = RBTMatrix::zeros(); // used as an accumulator for \sum_i w_i * M_i * M_{0,i}^{-1} int vertCount = static_cast<int>(m_vertices.size()); for(int i=0, offset=0; i<vertCount; ++i, offset+=3) { const Vector3d& p0 = m_vertices[i].position(); const std::vector<BoneWeight>& boneWeights = m_vertices[i].bone_weights(); int boneWeightCount = static_cast<int>(boneWeights.size()); Vector3d p; if(boneWeightCount != 0) { double boneWeightSum = 0; for(int j=0; j<boneWeightCount; ++j) { int boneIndex = boneWeights[j].bone_index(); double boneWeight = boneWeights[j].weight(); boneWeightSum += boneWeight; m->add_scaled(skinningMatrices[boneIndex], boneWeight); } // Note: This is effectively p = m*p0 (if we think of p0 as (p0.x, p0.y, p0.z, 1)). p = m->apply_to_point(p0); p /= boneWeightSum; // Reset the accumulator matrix ready for the next vertex. m->reset_to_zeros(); } else { // If this vertex is unaffected by the armature (ie no bone weights have been assigned to it), // use its rest position as its real position (it the best we can do). p = p0; } m_vertArray[offset] = px; m_vertArray[offset+1] = py; m_vertArray[offset+2] = pz; } } void Submesh::render() const { glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT); glPushAttrib(GL_ENABLE_BIT | GL_POLYGON_BIT); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_DOUBLE, 0, &m_vertArray[0]); if(m_material->uses_texcoords()) { glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_DOUBLE, 0, &m_texCoordArray[0]); } m_material->apply(); glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_vertIndices.size()), GL_UNSIGNED_INT, &m_vertIndices[0]); glPopAttrib(); glPopClientAttrib(); } 

Note that realistic implementations usually do such things on the GPU as far as I know.

+5
source share

Your code assumes that each bone has an independent transformation matrix (you reset your matrix at the beginning of each iteration of the loop). But in fact, the bones form a hierarchical structure, which you must preserve when rendering. Keep in mind that when your upper arm rotates, your forearm rotates together because it is attached. The forearm may have its own rotation, but it is applied after it is rotated using the shoulder.

Then the skeleton is rendered recursively. Here are a few pseudo codes:

 function renderBone(Bone b) { setupTransformMatrix(b); draw(b); foreach c in b.getChildren() renderBone(c); } main() { gl.glLoadIdentity(); renderBone(rootBone); } 

Hope this helps.

+1
source share

All Articles