7

我正在尝试将动画添加到我的程序中。

我在 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);
 }
4

2 回答 2

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 上执行此类操作。

于 2010-11-23T22:57:07.810 回答
1

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

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

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

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

我希望这有帮助。

于 2012-09-03T06:33:59.457 回答