如何使用Assimp让模型动画化?

时间:2015-02-18 23:16:47

标签: c++ animation assimp

目前,我正在尝试使用OpenGL在C ++中制作游戏引擎,并希望让3D动画能够正常运行。我被建议使用Assimp并且能够找到一个让静态模型工作的教程,但我不知道从哪里开始动画。我一直试图谷歌它,但没有找到任何有效的东西。如何修改代码以获取动画?推荐使用哪种文件格式?

这是我目前的代码:

//Mesh.h    
#include <string>

#include "glut\include\GL\glew.h"
#include "glut\include\GL\glut.h"

#include <assimp/Importer.hpp>      // C++ importer interface
#include <assimp/scene.h>           // Output data structure
#include <assimp/postprocess.h>     // Post processing fla

//textures
#include <SOIL.h>

class Mesh
{
public:
    Mesh(void);
    Mesh(std::string filename, std::string textureFilename, float x, float y, float z, float width, float height, float depth, float rotX, float rotY, float rotZ);
    ~Mesh(void);

    void Init(std::string filename);
    void LoadTexture(std::string textureName);
    void Draw();

private:
    GLfloat *vertexArray;
    GLfloat *normalArray;
    GLfloat *uvArray;

    GLint numVerts;

    GLuint m_Texture[1];

    float m_CenterX, m_CenterY, m_CenterZ, m_Width, m_Height, m_Depth;
    float m_XRotation, m_YRotation, m_ZRotation;
};

//Mesh.cpp
#include "Mesh.h"

Mesh::Mesh(void)
{
}

Mesh::Mesh(std::string filename, std::string textureFilename, float x, float y, float z, float width, float height, float depth, float rotX, float rotY, float rotZ)
{
    //fills in variables
    Init(filename);
    LoadTexture(textureFilename);
}

Mesh::~Mesh(void)
{

}

void Mesh::Init(std::string filename)
{
    Assimp::Importer importer;
    const aiScene *scene = importer.ReadFile(filename,aiProcessPreset_TargetRealtime_Fast);//aiProcessPreset_TargetRealtime_Fast has the configs you'll need

    aiMesh *mesh = scene->mMeshes[0]; //assuming you only want the first mesh

    numVerts = mesh->mNumFaces*3;

    vertexArray = new float[mesh->mNumFaces*3*3];
    normalArray = new float[mesh->mNumFaces*3*3];
    uvArray = new float[mesh->mNumFaces*3*2];

    for(unsigned int i=0;i<mesh->mNumFaces;i++)
    {
        const aiFace& face = mesh->mFaces[i];

        for(int j=0;j<3;j++)
        {
            aiVector3D uv = mesh->mTextureCoords[0][face.mIndices[j]];
            memcpy(uvArray,&uv,sizeof(float)*2);
            uvArray+=2;

            aiVector3D normal = mesh->mNormals[face.mIndices[j]];
            memcpy(normalArray,&normal,sizeof(float)*3);
            normalArray+=3;

            aiVector3D pos = mesh->mVertices[face.mIndices[j]];
            memcpy(vertexArray,&pos,sizeof(float)*3);
            vertexArray+=3;
        }
    }

    uvArray-=mesh->mNumFaces*3*2;
    normalArray-=mesh->mNumFaces*3*3;
    vertexArray-=mesh->mNumFaces*3*3;
}

void Mesh::LoadTexture(std::string textureName)         
{
    glGenTextures(1, &m_Texture[0]);
    glBindTexture(GL_TEXTURE_2D, m_Texture[0]);
    // Set our texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // Set texture filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);  // NOTE the GL_NEAREST Here! 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);  // NOTE the GL_NEAREST Here! 

    m_Texture[0] = SOIL_load_OGL_texture // load an image file directly as a new OpenGL texture
    (
        textureName.c_str(),
        SOIL_LOAD_AUTO,
        SOIL_CREATE_NEW_ID,
        SOIL_FLAG_MIPMAPS | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
    );
}

void Mesh::Draw()
{
    glPushMatrix();
        glTranslatef(m_CenterX, m_CenterY, m_CenterZ);

        glRotatef(m_XRotation, 1, 0, 0);
        glRotatef(m_YRotation, 0, 1, 0);
        glRotatef(m_ZRotation, 0, 0, 1);

        glScalef(m_Width, m_Height, m_Depth);

        glEnableClientState(GL_NORMAL_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnableClientState(GL_VERTEX_ARRAY);

            glNormalPointer(GL_FLOAT,0,normalArray);
            glTexCoordPointer(2,GL_FLOAT,0,uvArray);
            glVertexPointer(3,GL_FLOAT,0,vertexArray);

            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, m_Texture[0]);
            glDrawArrays(GL_TRIANGLES,0,numVerts);

        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisableClientState(GL_VERTEX_ARRAY);
    glPopMatrix();
}

1 个答案:

答案 0 :(得分:5)

这是一个古老的问题,但是我确信它可能会在将来被其他人使用,因此我将尝试概述使用Assimp库进行动画处理时所具有的一些选项。

首先,我想提一提,您可以在没有Assimp库的情况下进行动画处理。该库只是为您提供了一种加载模型的好方法,但是您发现它不会为您做动画。

从概念上讲,无论您是否使用Assimp,动画都将非常相似。例如;如果您编写了自己的模型加载器,则可以轻松地使用它而不是Assimp并仍然可以进行动画处理。但是,由于制作动画的方式不止一种,因此实现动画的方式可能会受到更多限制,因为在没有Assimp的情况下进行骨架动画将涉及编写模型加载器,该加载器可以从中获取骨骼变换,权重和各种数据模型文件,这可能需要一段时间。

有多种制作动画的方法;无论是技术方面,还是您是否想通过硬件加速(在GPU或CPU上)来实现。我要提到的是这里的一些选项,因为大多数人都使用Assimp来做骨骼动画,如果您的数学技能不强并且只想要易于组合的东西,这可能会令人生畏。 / p>

通常有三种接受的动画制作方法:

  1. 关键帧
  2. 带插值的Keframe
  3. 骨骼动画(硬件蒙皮)

关键帧

关键帧动画是当您为动画的每一帧创建单独的模型时,类似于2D精灵表。您可以连续渲染模型以产生动画。该方法可能是最简单但最幼稚的实现,因为您需要为每个动画加载多个模型。这些框架之间的过渡可能会很明显,具体取决于您生成的框架数量,并且在看起来可接受之前,您可能需要导出几个模型文件。 这种方法的另一个缺点是您可能需要生成自己的模型。

具有插值的关键帧

此方法与上述方法类似,但是不是将每个关键帧生成为单独的模型,而是仅生成几个关键帧,并使用内插引擎生成“缺失”模型。之所以可以这样做,是因为如果知道顶点的起点和终点,我们可以进行插值以找出在时间= t处顶点应位于的位置。

本教程在解释如何制作关键帧动画方面做得很好:

https://www.khronos.org/opengl/wiki/Keyframe_Animation

同样,它没有讨论Assimp,但是概念相同,您仍然可以使用Assimp加载模型。 这种动画形式非常容易实现,对初学者来说非常好。但是,它确实有一些缺点。 如果您选择这种方式,则可能会受到内存的限制,因为此方法在VBO中会占用大量内存,这取决于模型的详细程度。 如果选择创建自己的模型,则还需要保留模型文件中的顶点顺序,以便在一个模型文件(关键帧1)的顶点2到另一个模型文件(关键帧2)的顶点2之间进行插值正确。

骨骼动画

这可能是制作动画最困难的方法,但是它处理了方法1和方法2中的许多问题。您还会发现,通过执行骨骼动画,您可以加载许多新的文件格式;用于指定骨骼变形和旋转的对象,而不必为每个关键帧加载新文件。

在这种情况下,我认为拥有Assimp将大有裨益。 Assimp非常精通,可以处理从模型文件中获取所需数据以进行骨骼动画的工作。

如果您对做骨骼动画感兴趣,那么本教程是一种绝佳的实现方法,甚至还可以使用Assimp。

http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

我本人使用本教程在自己的引擎中实现了骨骼动画。因此,如果您决定走这条路,我强烈建议您阅读此书。

我要提到的最后一件事我使某些人感到困惑,那就是可以使用硬件加速来完成动画,但这并不是绝对必要的。

在我提供的上一个教程链接中,这两个都是使用硬件加速进行动画处理的。在这种情况下,这意味着顶点计算是在顶点着色器主体中的GPU上完成的。

但是我知道很多人可能不熟悉现代OpenGL,在这种情况下,您仍然可以在CPU上进行这些相同的计算。此处的想法是查看顶点着色器中发生的情况,并创建一个为您执行这些计算的函数。

您还询问了动画的文件格式;这将取决于您执行动画的路线。如果要对.fbx和.md5等格式进行动画处理,则可能需要进行骨骼动画处理。如果您选择关键帧动画,我可能会坚持使用.obj,这是我发现最易于使用的格式,因为格式说明非常容易理解。

在调试引擎中的动画时,请确保您有一个知道有效的文件;另一个陷阱是,在互联网上下载免费模型可以包含任何旧格式,绝对纹理路径和不同的坐标系(Y向上或Z向上)等。