Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGL skeleton animation

I am trying to add animation to my program.

I have human model created in Blender with skeletal animation, and I can skip through the keyframes to see the model walking.

Now I've exported the model to an XML (Ogre3D) format, and in this XML file I can see the rotation, translation and scale assigned to each bone at a specific time (t=0.00000, t=0.00040, ... etc.)

What I've done is found which vertices are assigned each bone. Now I'm assuming all I need to do is apply the transformations defined for the bone to each one of these vertices. Is this the correct approach?

In my OpenGL draw() function (rough pseudo-code):

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);
 }
like image 409
14 revs, 12 users 16% Avatar asked Nov 23 '10 22:11

14 revs, 12 users 16%


2 Answers

Vertices are usually affected by more than one bone -- it sounds like you're after linear blend skinning. My code's in C++ unfortunately, but hopefully it'll give you the 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's 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 (i.e. no bone weights have been assigned to it),
            // use its rest position as its real position (it's the best we can do).
            p = p0;
        }

        m_vertArray[offset] = p.x;
        m_vertArray[offset+1] = p.y;
        m_vertArray[offset+2] = p.z;
    }
}

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 in passing that real-world implementations usually do this sort of thing on the GPU to the best of my knowledge.

like image 161
Stuart Golodetz Avatar answered Oct 19 '22 02:10

Stuart Golodetz


Your code assumes that each bone has an independent transformation matrix (you reset your matrix at the start of each loop iteration). But in reality, bones form a hierarchical structure that you must preserve when you do your rendering. Consider that when your upper arm rotates your forearm rotates along, because it is attached. The forearm may have its own rotation, but that is applied after it is rotated with the upper arm.

The rendering of the skeleton then is done recursively. Here is some pseudo-code:

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

main() {
    gl.glLoadIdentity();
    renderBone(rootBone);
}

I hope this helps.

like image 34
Miguel Avatar answered Oct 19 '22 02:10

Miguel