[OpenGL] 使用Assimp导入模型(Qt)

       最近终于决定要在自己的demo中加入模型了!本次选择的是开源库Assimp,之前一直嫌麻烦没有去落实这件事,但实际上,assimp的配置意外的没有我想象中的那么麻烦。

       参考这篇文章基本上可以配置成功https://learnopengl.com/Model-Loading/Assimp,它所提到的坑我都遇到了。

       这里是Assimp下载地址:http://assimp.sourceforge.net/main_downloads.html

       下载源码后,需要使用cmake进行编译,在上方选择源码位置,和build工程的位置,如果没有什么特殊的配置需求的话,直接按顺序依次点1,2,3的按钮即可。Configure的时候,可能会遇到一个dx的error,按照引用文章的提示,直接在http://www.microsoft.com/en-us/download/details.aspx?id=6812下载相关组件,安装中还可能遇到s1023的错误代码,此处可在命令行输入(卸载vs的一些组件):

MsiExec.exe /passive /X{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5}
MsiExec.exe /passive /X{1D8E6291-B0D5-35EC-8441-6616F567A0F7}

       

      这里特别需要注意的是,cmake的generator选择,需要和最终调用assimp的编译器匹配。比如,我的Qt版本为:5.11.2 MSVC2017 64bit。

     

       也就是我使用了vs的msvc作为c++的编译器,并且是2017版本,64位的。那么相应的,如cmake界面右下角红色框所标出的,generator选择Visual Studio 15 2017 Win64

      编译完成之后,打开对应工程,切到release模式,然后点编译。之后,可以在code/Release得到我们所需的lib和dll(用不同的编译器得到的名称会不太一样):

        

       之后,我们在Qt的pro文件处,新建include文件夹,把源码中的include内容复制过去;新建lib文件夹,把assimpxxx.lib放到该文件夹下。最后,把assimpxxx.dll放到生成的exe所在的文件夹下。(如果设定了shadow build和构建目录,那么就在这一目录下)。

        最后,在.pro按如下写好lib和include的连接:

INCLUDEPATH += include

LIBS += -L$$PWD/lib/ \
        -lassimp-vc140-mt

       配置完成后,可以开始导入代码的编写。Assimp仅仅是实现了导入相关的功能,这些导入的数据实际上要如何使用,是需要额外实现的。

       由于我的材质是动态加载和替换的,所以此处只导入了顶点、法线和纹理坐标:

      (5.15更新,之前的代码只能加载一个mesh,改进了一下,不过每个mesh只支持一个贴图)

#ifndef GEOMETRYENGINE_H
#define GEOMETRYENGINE_H

#include <QOpenGLExtraFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <QOpenGLTexture>

using namespace std;
struct VertexData
{
    QVector3D position;
    QVector3D tangent;
    QVector3D normal;
    QVector2D texcoord;
};

struct MeshBuffer
{
    QOpenGLBuffer arrayBuf;
    QOpenGLBuffer indexBuf;
    int vertexNum;
    int indiceNum;

    MeshBuffer() : indexBuf(QOpenGLBuffer::IndexBuffer)
    {
        arrayBuf.create();
        indexBuf.create();
    }

    ~MeshBuffer()
    {
        arrayBuf.destroy();
        indexBuf.destroy();
    }

    void Init(VertexData* vertex, int num)
    {
        vertexNum = num;
        arrayBuf.bind();
        arrayBuf.allocate(vertex, vertexNum * static_cast<int>(sizeof(VertexData)));
    }

    void Init(GLushort* indice, int num)
    {
        indiceNum = num;
        indexBuf.bind();
        indexBuf.allocate(indice, indiceNum * static_cast<int>(sizeof(GLushort)));
    }

    void bind()
    {
        arrayBuf.bind();
        indexBuf.bind();
    }
};

struct Mesh
{
    string name;
    MeshBuffer* buffer = nullptr;
    QOpenGLTexture* albedo;
};

class Model
{
private:
    vector<Mesh*> vecMesh;
public:
    void Push(Mesh* mesh)
    {
        vecMesh.emplace_back(mesh);
    }
    MeshBuffer* GetMeshBuffer(size_t idx) { return vecMesh[idx]->buffer;}
    Mesh* GetMesh(size_t idx) { return vecMesh[idx];}
    size_t Count() { return vecMesh.size();}
};

class GeometryEngine
{
public:
    GeometryEngine();
    virtual ~GeometryEngine();

    bool loadObj(string path, Model*& pModel);
    void drawObj(string path,QOpenGLShaderProgram* program,bool bTess = false);
    void drawObj(MeshBuffer* meshBuffer, QOpenGLShaderProgram* program,bool bTess = false);


    void CalTangent(VertexData& vertex0, VertexData& vertex1, VertexData& vertex2);

private:
    void processNode(const string& path, aiNode *node, const aiScene *scene);
    void processMesh(vector<VertexData>& vertices, vector<GLushort>& indices, QOpenGLTexture*& albedo, aiMesh *mesh, const aiScene *scen);

    map<string, Model> mapModel;
};

#endif // GEOMETRYENGINE_H

#include "geometryengine.h"
#include "resourceinfo.h"
#include <QVector2D>
#include <QVector3D>
#include <QVector>


GeometryEngine::GeometryEngine()
{

}

GeometryEngine::~GeometryEngine()
{

}

bool GeometryEngine::loadObj(string path, Model*& pModel)
{
    if(mapModel.find(path) != mapModel.end())
    {
        return true;
    }

    Assimp::Importer import;
    const aiScene *scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

    if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
    {
        qDebug() << "ERROR::ASSIMP::" << import.GetErrorString() ;
        return false;
    }
    string directory = path.substr(0, path.find_last_of('/'));

    processNode(path, scene->mRootNode, scene);
    pModel = &mapModel[path];
    return true;
}

void GeometryEngine::drawObj(MeshBuffer* meshBuffer, QOpenGLShaderProgram* program,bool bTess)
{
    meshBuffer->bind();

    auto gl = QOpenGLContext::currentContext()->extraFunctions();

    int offset = 0;

    int vertexLocation = program->attributeLocation("a_position");
    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));

    offset += sizeof(QVector3D);

    int tangentLocation = program->attributeLocation("a_tangent");
    program->enableAttributeArray(tangentLocation);
    program->setAttributeBuffer(tangentLocation, GL_FLOAT, offset, 3, sizeof(VertexData));

    offset += sizeof(QVector3D);

    int normalLocation = program->attributeLocation("a_normal");
    program->enableAttributeArray(normalLocation);
    program->setAttributeBuffer(normalLocation, GL_FLOAT, offset, 3, sizeof(VertexData));

    offset += sizeof(QVector3D);

    int texcoordLocation = program->attributeLocation("a_texcoord");
    program->enableAttributeArray(texcoordLocation);
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));

    if(bTess)
    {
        gl->glPatchParameteri(GL_PATCH_VERTICES, 3);
        gl->glDrawElements(GL_PATCHES, meshBuffer->indiceNum, GL_UNSIGNED_SHORT, nullptr);
    }
    else
    {
        gl->glDrawElements(GL_TRIANGLES, meshBuffer->indiceNum, GL_UNSIGNED_SHORT, nullptr);
    }
}


void GeometryEngine::drawObj(string path,QOpenGLShaderProgram* program,bool bTess)
{
    Model* pModel;
    if(mapModel.find(path) == mapModel.end())
    {
        if(!loadObj(path, pModel))
        {
            return;
        }
    }

    auto vecMesh = mapModel[path];
    for(size_t i = 0;i < vecMesh.Count();i++)
    {
        auto meshBuffer = vecMesh.GetMeshBuffer(i);
        drawObj(meshBuffer,program, bTess);
    }
}

void GeometryEngine::processMesh(vector<VertexData>& vertices, vector<GLushort>& indices, QOpenGLTexture*& albedo, aiMesh *mesh, const aiScene *scene)
{
    for(unsigned int i = 0; i < mesh->mNumVertices; i++)
    {
        VertexData vertex;
        if(mesh->mVertices)
        {
            vertex.position = QVector3D(mesh->mVertices[i].x,mesh->mVertices[i].y,mesh->mVertices[i].z);
        }
        if(mesh->mTextureCoords[0])
        {
            vertex.texcoord = QVector2D(mesh->mTextureCoords[0][i].x,mesh->mTextureCoords[0][i].y);
        }
        if(mesh->mNormals)
        {
            vertex.normal = QVector3D(mesh->mNormals[i].x,mesh->mNormals[i].y,mesh->mNormals[i].z);
            vertex.normal.normalized();
        }
        if(mesh->mTangents)
        {
            vertex.tangent = QVector3D(mesh->mTangents[i].x,mesh->mTangents[i].y,mesh->mTangents[i].z);
        }
        vertices.push_back(vertex);
    }

    for(unsigned int i = 0; i < mesh->mNumFaces; i++)
    {
        aiFace face = mesh->mFaces[i];
        for(unsigned int j = 0; j < face.mNumIndices; j++)
        {
            indices.push_back(static_cast<GLushort>(face.mIndices[j]));
        }
    }

    aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
    for(unsigned int i = 0; i < material->GetTextureCount(aiTextureType_DIFFUSE); i++)
    {
        aiString str;
        material->GetTexture(aiTextureType_DIFFUSE, i, &str);
        albedo = CResourceInfo::Inst()->CreateTexture(str.C_Str());
    }
}



void GeometryEngine::processNode(const string& path, aiNode *node, const aiScene *scene)
{
    // process all the node's meshes (if any)
    for(unsigned int i = 0; i < node->mNumMeshes; i++)
    {
        vector<VertexData> vertices;
        vector<GLushort> indices;
        QOpenGLTexture* albedo = nullptr;
        aiMesh *aimesh = scene->mMeshes[node->mMeshes[i]];
        processMesh(vertices, indices, albedo, aimesh, scene);

        MeshBuffer* meshBuffer = new MeshBuffer();
        for(size_t i = 0;i < indices.size() / 3; i++)
        {
            CalTangent(vertices[indices[3 * i]],vertices[indices[3 * i + 1]],vertices[indices[3 * i + 2]]);
        }

        meshBuffer->Init(vertices.data(),static_cast<int>(vertices.size()));
        meshBuffer->Init(indices.data(),static_cast<int>(indices.size()));

        Mesh* mesh = new Mesh();
        mesh->name = node->mName.C_Str();
        mesh->buffer = meshBuffer;
        mesh->albedo = albedo;
        mapModel[path].Push(mesh);
    }

    // then do the same for each of its children
    for(unsigned int i = 0; i < node->mNumChildren; i++)
    {
        processNode(path, node->mChildren[i], scene);
    }
}


void GeometryEngine::CalTangent(VertexData& vertex0, VertexData& vertex1, VertexData& vertex2)
{
    float u0 = vertex0.texcoord.x();
    float v0 = vertex0.texcoord.y();

    float u1 = vertex1.texcoord.x();
    float v1 = vertex1.texcoord.y();

    float u2 = vertex2.texcoord.x();
    float v2 = vertex2.texcoord.y();

    float t1 = u1 - u0;
    float b1 = v1 - v0;

    float t2 = u2 - u0;
    float b2 = v2 - v0;

    QVector3D e0 = vertex1.position - vertex0.position;
    QVector3D e1 = vertex2.position - vertex0.position;

    float k = t1 * b2 - b1 * t2;

    QVector3D tangent;
    tangent = k * QVector3D(b2 * e0.x() - b1 * e1.x(),b2 * e0.y() - b1 * e1.y(),b2 * e0.z() - b1 * e1.z());

    QVector<VertexData*> vertexArr = { &vertex0, &vertex1, &vertex2};
    QVector<int> adjoinPlane;
    adjoinPlane.resize(vertexArr.size());
    for(int i = 0;i < vertexArr.size();i++)
    {
        adjoinPlane[i]++;
        float ratio = 1.0f / adjoinPlane[i];
        vertexArr[i]->tangent = vertexArr[i]->tangent * (1 - ratio) + tangent * ratio;
        vertexArr[i]->tangent.normalize();
    }
}

        接下来,我们可以试着导入一个简单的模型。

        在建模软件maya中,拖出一个简单的甜甜圈,并勾选使用三角面显示:

        

        给这个甜甜圈绑定一个uv,此处uv用了一个圆柱形投影,将其水平扫描设为360度,高度稍微调高一些。这里只是大致得到一个还算过得去的uv纹理。

         

        选中当前对象,然后在文件菜单选择导出当前选择,然后根据自己的需求导出:

       

      然后就可以把这个"甜甜圈”导入自己的项目中了:

      

        试着加一下材质,除了有一块因为瞎搞的uv导致不太对的地方,其它看起来都没什么问题了。

       

 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值