Скелетная анимация OpenGL - PullRequest
7 голосов
/ 24 ноября 2010

Я пытаюсь добавить анимацию в мою программу.

У меня есть модель человека, созданная в Blender со скелетной анимацией, и я могу пропустить ключевые кадры, чтобы увидеть ходящую модель.

Теперь я экспортировал модель в формат XML (Ogre3D), и в этом файле XML я вижу вращение, перемещение и масштаб, назначенные каждой кости в определенное время (t = 0,00000, t = 0,00040,. .. и т. д.)

Я обнаружил, какие вершины назначены каждой кости. Теперь я предполагаю, что все, что мне нужно сделать, это применить преобразования, определенные для кости, к каждой из этих вершин. Это правильный подход?

В моей функции draw () OpenGL (грубый псевдокод):

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 ]

5 голосов
/ 24 ноября 2010

На вершины обычно влияет более чем одна кость - звучит так, как будто вы после линейного смешивания кожи.К сожалению, мой код на 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 голос
/ 03 сентября 2012

Ваш код предполагает, что каждая кость имеет независимую матрицу преобразования (вы сбрасываете свою матрицу в начале каждой итерации цикла).Но на самом деле кости образуют иерархическую структуру, которую вы должны сохранить при выполнении рендеринга.Учтите, что когда ваше плечо вращается, предплечье вращается вперед, потому что оно прикреплено.Предплечье может иметь свое собственное вращение, но оно применяется после того, как оно вращается с плечом.

Рендеринг скелета выполняется рекурсивно.Вот некоторый псевдокод:

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

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

Надеюсь, это поможет.

...