3

目前,我正在尝试使用 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();
}
4

1 回答 1

11

这是一个老问题,但我确信它可能对其他人将来有用,所以我将尝试概述一些使用 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 向上或 Z 向上)等。

于 2018-08-15T11:55:40.187 回答