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

Why*_*ork 3 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();
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*der 6

这是一个老问题,但我确信它可能会在将来用于其他人,所以我将尝试概述一些使用Assimp库做动画的选项.

首先,我想提一下你可以在没有Assimp库的情况下做动画.该库只为您提供加载模型的好方法,但正如您所发现的那样,它不会为您创建动画.

无论你是否使用Assimp,概念上动画都会非常相似.例如; 如果你已经编写了自己的模型加载器,你可以轻松地使用它而不是Assimp,并且仍然可以做动画.然而,由于是做动画的方法不止一种,你可能会在你能达到它做骨骼动画不Assimp的方式将涉及编写的模型加载器,可以让骨转换,权重和各种数据出较为有限模型文件,这可能需要一段时间.

有多种方法可以做动画; 技术方面以及是否要通过硬件加速(在GPU或CPU上)来实现.我要提几个你在这里,因为大多数人使用Assimp做骨骼动画,如果你不强你的数学技能,只是想要的东西,很容易放在一起,可以是相当吓人的选项.

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

  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为up或Z为up)等.