OpenGL骨架动画

时间:2010-11-23 22:49:13

标签: opengl skeletal-animation

我正在尝试为我的程序添加动画。

我在Blender中使用骨骼动画创建了人体模型,我可以跳过关键帧来查看模型行走。

现在我已经将模型导出为XML(Ogre3D)格式,在这个XML文件中,我可以看到在特定时间分配给每个骨骼的旋转,平移和缩放(t = 0.00000,t = 0.00040 ,. ..等等。)

我所做的是找到为每个骨骼分配的顶点。现在我假设我需要做的就是将为骨骼定义的变换应用于这些顶点中的每一个。这是正确的方法吗?

在我的OpenGL draw()函数中(粗伪伪代码):

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);
 }

2 个答案:

答案 0 :(得分:5)

顶点通常会受到多个骨骼的影响 - 听起来就像是在线性混合蒙皮之后。不幸的是,我的代码是用C ++编写的,但希望它会给你一个想法:

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();
}

请注意,实际的实现通常会在GPU上做到这一点,据我所知。

答案 1 :(得分:1)

您的代码假设每个骨骼都有一个独立的变换矩阵(您在每次循环迭代开始时重置矩阵)。但实际上,骨骼形成了一个层次结构,在渲染时必须保留这些结构。考虑到当你的上臂旋转时,你的前臂会一直旋转,因为它是附着的。前臂可能有自己的旋转,但是在它与上臂一起旋转后应用。

然后递归地完成骨架的渲染。这是一些伪代码:

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

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

我希望这会有所帮助。