OpenGL model loading

1. Model loading library

The Assimp library can import many different model file formats (and also export some of them), and it loads all model data into Assimp's common data structures.

When using Assimp to import a model, it usually loads the entire model into a Scene object, which will contain all the data in the imported model/scene . Assimp will load the scene as a series of nodes (Node), each node contains the index of the data stored in the scene object, and each node can have any number of child nodes. A (simplified) model of the Assimp data structure is as follows:

  • Like materials and meshes, all scene/model data is contained in the Scene object. The Scene object also contains a reference to the root node of the scene.
  • The Root node of the scene (root node) may contain child nodes (like other nodes), which will have a series of indexes pointing to the mesh data stored in the mMeshes array in the scene object. The mMeshes array under the Scene stores the real Mesh object, and the mMeshes array in the node stores only the index of the grid array in the scene.
  • A Mesh object itself contains all relevant data needed for rendering, such as vertex positions, normal vectors, texture coordinates, faces (Face) and the material of the object.
  • A mesh contains multiple faces. Face represents the rendering primitive (triangle, square, point) of the object. A face contains the indices of the vertices that make up the primitive. Since vertices and indices are separated, rendering with an index buffer is very simple.
  • Finally, a mesh also contains a Material object, which contains functions that allow us to access the material properties of the object, such as color and texture maps (such as diffuse and specular maps).

2. Compile the Assimp library

Download library version assimp-5.1.0

GitHub - assimp/assimp: The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clean data structure.The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clean data structure. - GitHub - assimp/assimp: The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clean data structure.https://github.com/assimp/assimp准备好cmake,VisualStudio。

Start compiling: 

Note that the compiled 64/32-bit version matches the version of the application. 

The following is the compiled library path, which requires bin, lib library, and files under include. Copy the library files under bin and lib, and the include/assimp/config.h file to the project path.

Then copy the source path include/assimp to the project directory.

3. Custom grid class (Mesh)

A Mesh represents a single drawable entity. A mesh should at least need a series of vertices, each vertex contains a position vector, a normal vector and a texture coordinate vector. A mesh should also contain indices for indexed painting and material data in the form of textures (diffuse/specular maps).

#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions_3_3_Core>
#include <string>
#include <vector>
using namespace std;
struct Vertex {
    QVector3D Position; //位置
    QVector3D Normal;   //法向量
    QVector2D TexCoords;    //纹理坐标
};
struct Texture {
    unsigned int id;    //纹理ID
    string type;    //类型
    string path;    //路径
};

class Mesh {
public:
    // mesh data
    vector<Vertex> vertices;
    vector<unsigned int> indices;
    vector<Texture> textures;

    void Draw(QOpenGLShaderProgram &shader);
    Mesh(QOpenGLFunctions_3_3_Core *glFuns,
         vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures);
private:
    // render data
    unsigned int VAO, VBO, EBO;
    void setupMesh();
private:
    QOpenGLFunctions_3_3_Core *m_glFuns;
};



#include "mesh.h"

Mesh::Mesh(QOpenGLFunctions_3_3_Core *glFuns,
           vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
    :m_glFuns(glFuns)
{
    this->vertices = vertices;
    this->indices = indices;
    this->textures = textures;
    setupMesh();
}

void Mesh::setupMesh()
{
    //创建VBO和VAO对象,并赋予ID
    m_glFuns->glGenVertexArrays(1, &VAO);
    m_glFuns->glGenBuffers(1, &VBO);
    m_glFuns->glGenBuffers(1,&EBO);
    //绑定VBO和VAO对象
    m_glFuns->glBindVertexArray(VAO);
    m_glFuns->glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //为当前绑定到target的缓冲区对象创建一个新的数据存储。
    //如果data不是NULL,则使用来自此指针的数据初始化数据存储
    m_glFuns->glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(Vertex),
                           &vertices[0], GL_STATIC_DRAW);
    m_glFuns->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    m_glFuns->glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                           indices.size() * sizeof(unsigned int),&indices[0], GL_STATIC_DRAW);
    //告知显卡如何解析缓冲里的属性值
    m_glFuns->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
    m_glFuns->glEnableVertexAttribArray(0);
    m_glFuns->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
                                    (void*)offsetof(Vertex, Normal));

    m_glFuns->glEnableVertexAttribArray(1);
    m_glFuns->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
                                    (void*)offsetof(Vertex, TexCoords));
    m_glFuns->glEnableVertexAttribArray(2);
}


void Mesh::Draw(QOpenGLShaderProgram &shader)
{
    unsigned int diffuseNr = 1;
    unsigned int specularNr = 1;
    for(unsigned int i = 0; i < textures.size(); i++) {
        m_glFuns->glActiveTexture(GL_TEXTURE0 + i);
        string number;
        string name = textures[i].type;
        if(name == "texture_diffuse")
            number = std::to_string(diffuseNr++);
        else if(name == "texture_specular")
            number = std::to_string(specularNr++);

        shader.setUniformValue(("material." + name + number).c_str(), i);
        m_glFuns->glBindTexture(GL_TEXTURE_2D, textures[i].id);

    }
    m_glFuns->glBindVertexArray(VAO);
    //m_glFuns->glDrawArrays(GL_TRIANGLES, 0, vertices.size());
    m_glFuns->glDrawElements(GL_TRIANGLES,indices.size(),GL_UNSIGNED_INT,0);
}

4. Custom model class (Model)

A model contains multiple meshes, or even multiple object models. Use Assimp to load the model and convert it to multiple Mesh objects.

The Model class contains a vector of Mesh objects, and the constructor requires us to give it a file path. In the constructor it will load the file directly through loadModel.

#ifndef MODEL_H
#define MODEL_H

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#include "mesh.h"
#include <QOpenGLTexture>
class Model {
public:
    float m_maxX=-100.0;
    float m_maxY=-100.0;
    float m_minX=100.0;
    float m_minY=100.0;
    vector<Texture> textures_loaded;
    Model(QOpenGLFunctions_3_3_Core *glfuns,const char *path) :m_glFuns(glfuns) {

        loadModel(path);
    }
    void Draw(QOpenGLShaderProgram &shader) {
        for(unsigned int i = 0; i < meshes.size(); i++)
            meshes[i].Draw(shader);

    }

private:
    // model data
    QOpenGLFunctions_3_3_Core *m_glFuns;
    vector<Mesh> meshes;
    string directory;
    void loadModel(string path);

    void processNode(aiNode *node, const aiScene *scene);
    Mesh processMesh(aiMesh *mesh, const aiScene *scene);
    vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName);
    unsigned int TextureFromFile(const char *path, const string &directory);
};

#endif // MODEL_H



#include "model.h"

void Model::loadModel(string path)
{
    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;
    }
    directory = path.substr(0, path.find_last_of('/'));
    processNode(scene->mRootNode, scene);
}

void Model::processNode(aiNode *node, const aiScene *scene)
{
    // process all the node's meshes (if any)
    for(unsigned int i = 0; i < node->mNumMeshes; i++) {
        aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        meshes.push_back(processMesh(mesh, scene));
    }
    // then do the same for each of its children
    for(unsigned int i = 0; i < node->mNumChildren; i++) {
        processNode(node->mChildren[i], scene);
    }
}

Mesh Model::processMesh(aiMesh *mesh, const aiScene *scene)
{
    vector<Vertex> vertices;
    vector<unsigned int> indices;
    vector<Texture> textures;
    for(unsigned int i = 0; i < mesh->mNumVertices; i++) {
        if(m_maxX<mesh->mVertices[i].x) m_maxX=mesh->mVertices[i].x;
        if(m_maxY<mesh->mVertices[i].y) m_maxY=mesh->mVertices[i].y;
        if(m_minX>mesh->mVertices[i].x) m_minX=mesh->mVertices[i].x;
        if(m_minY>mesh->mVertices[i].y) m_minY=mesh->mVertices[i].y;
        Vertex vertex;
        // 处理顶点位置、法线和纹理坐标
        QVector3D vector;
        vector.setX(mesh->mVertices[i].x);
        vector.setY(mesh->mVertices[i].y);
        vector.setZ(mesh->mVertices[i].z);
        vertex.Position = vector;

        vector.setX(mesh->mNormals[i].x);
        vector.setY(mesh->mNormals[i].y);
        vector.setZ(mesh->mNormals[i].z);
        vertex.Normal = vector;

        if(mesh->mTextureCoords[0]) // 有纹理坐标?
        {
            QVector2D vec;
            vec.setX(mesh->mTextureCoords[0][i].x);
            vec.setY(mesh->mTextureCoords[0][i].y);
            vertex.TexCoords = vec;
        } else
            vertex.TexCoords = QVector2D(0.0f, 0.0f);

        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(face.mIndices[j]);
    }

    // 处理材质
    if(mesh->mMaterialIndex >= 0) {
        aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
        vector<Texture> diffuseMaps =
                loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
        textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
        vector<Texture> specularMaps =
                loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
        textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
    }
    return Mesh(m_glFuns,vertices, indices, textures);

}

vector<Texture> Model::loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName)
{
    vector<Texture> textures;
    for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) {
        aiString str;
        mat->GetTexture(type, i, &str);
        bool skip = false;
        for(unsigned int j = 0; j < textures_loaded.size(); j++) {
            if(std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) {
                textures.push_back(textures_loaded[j]);
                skip = true;
                break;
            }
        }
        if(!skip){
            Texture texture;
            texture.id = TextureFromFile(str.C_Str(), directory);
            texture.type = typeName;
            texture.path = str.C_Str();
            textures.push_back(texture);
            textures_loaded.push_back(texture);
        }
    }
    return textures;
}

unsigned int Model::TextureFromFile(const char *path, const string &directory)
{
    string filename = string(path);
    filename = directory + '/' + filename;

    QOpenGLTexture * texture=new QOpenGLTexture(QImage(filename.c_str()).mirrored());
    if(texture==NULL) qDebug()<<"texture is NULL";
    else qDebug()<<filename.c_str()<<"loaded";

    return texture->textureId();
}

5. Load the model

#include "axbopemglwidget.h"

const unsigned int timeOutmSec=50;
unsigned int VAO,VBO,lightVAO;
QVector3D lightPos(1.2f, 1.0f, 2.0f);
QVector3D lightColor(1.0f, 1.0f, 1.0f);
QVector3D objectColor(1.0f, 0.5f, 0.31f);
QVector3D viewInitPos(0.0,0.0,5.0);
AXBOpemglWidget::AXBOpemglWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    connect(&m_timer,SIGNAL(timeout()),this,SLOT(on_timeout()));
    m_timer.start(timeOutmSec);
    m_time.start();
    m_camera.Position=viewInitPos;
    setFocusPolicy(Qt::StrongFocus);
    //setMouseTracking(true);
}

AXBOpemglWidget::~AXBOpemglWidget()
{
    makeCurrent();
    glDeleteBuffers(1,&VBO);
    glDeleteVertexArrays(1,&VAO);
    glDeleteVertexArrays(1,&lightVAO);
    doneCurrent();
}

void AXBOpemglWidget::loadModel(string path)
{
    if(m_model !=NULL)
        delete m_model;

    m_model=NULL;
    makeCurrent();
    m_model=new Model(QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>()
                ,path.c_str());
    m_camera.Position=cameraPosInit(m_model->m_maxY,m_model->m_minY);
    doneCurrent();
}

void AXBOpemglWidget::initializeGL()
{
    initializeOpenGLFunctions();
    //创建VBO和VAO对象,并赋予ID
    bool success;
    m_ShaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shaders/shapes.vert");
    m_ShaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shaders/shapes.frag");
    success=m_ShaderProgram.link();
    if(!success) qDebug()<<"ERR:"<<m_ShaderProgram.log();   
}

void AXBOpemglWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);
    Q_UNUSED(h);
}

void AXBOpemglWidget::paintGL()
{
    if(m_model==NULL) return;
    QMatrix4x4 model;
    QMatrix4x4 view;
    QMatrix4x4 projection;
   // float time=m_time.elapsed()/50.0;
    projection.perspective(m_camera.Zoom,(float)width()/height(),0.1,100);
    view=m_camera.GetViewMatrix();
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    m_ShaderProgram.bind();
    m_ShaderProgram.setUniformValue("projection", projection);
    m_ShaderProgram.setUniformValue("view", view);
    //model.rotate(time, 1.0f, 1.0f, 0.5f);

    m_ShaderProgram.setUniformValue("viewPos",m_camera.Position);

    // light properties, note that all light colors are set at full intensity
    m_ShaderProgram.setUniformValue("light.ambient", 0.4f, 0.4f, 0.4f);
    m_ShaderProgram.setUniformValue("light.diffuse", 0.9f, 0.9f, 0.9f);
    m_ShaderProgram.setUniformValue("light.specular", 1.0f, 1.0f, 1.0f);
    // material properties
    m_ShaderProgram.setUniformValue("material.shininess", 32.0f);
    m_ShaderProgram.setUniformValue("light.direction", -0.2f, -1.0f, -0.3f);
    m_ShaderProgram.setUniformValue("model", model);
    m_model->Draw(m_ShaderProgram);
}

void AXBOpemglWidget::wheelEvent(QWheelEvent *event)
{
    m_camera.ProcessMouseScroll(event->angleDelta().y()/120);
}

void AXBOpemglWidget::keyPressEvent(QKeyEvent *event)
{
    float deltaTime=timeOutmSec/1000.0;

    switch (event->key()) {
    case Qt::Key_W: m_camera.ProcessKeyboard(FORWARD,deltaTime);break;
    case Qt::Key_S: m_camera.ProcessKeyboard(BACKWARD,deltaTime);break;
    case Qt::Key_D: m_camera.ProcessKeyboard(RIGHT,deltaTime);break;
    case Qt::Key_A: m_camera.ProcessKeyboard(LEFT,deltaTime);break;
    case Qt::Key_Q: m_camera.ProcessKeyboard(DOWN,deltaTime);break;
    case Qt::Key_E: m_camera.ProcessKeyboard(UP,deltaTime);break;
    case Qt::Key_Space: m_camera.Position=viewInitPos;break;

    default:break;
    }
}

void AXBOpemglWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons() & Qt::RightButton){
        static QPoint lastPos(width()/2,height()/2);
        auto currentPos=event->pos();
        QPoint deltaPos=currentPos-lastPos;
        lastPos=currentPos;

        m_camera.ProcessMouseMovement(deltaPos.x(),-deltaPos.y());
    }
}

void AXBOpemglWidget::on_timeout()
{
    update();
}

QVector3D AXBOpemglWidget::cameraPosInit(float maxY, float minY)
{
    QVector3D temp={0,0,0};
    float height=maxY-minY;
    temp.setZ(1.5*height);
    if(minY>=0)
        temp.setY(height/2.0);
    viewInitPos=temp;
    return temp;
}

Complete project:

https://download.csdn.net/download/wzz953200463/87919672icon-default.png?t=N5F7https://download.csdn.net/download/wzz953200463/87919672 

 

Guess you like

Origin blog.csdn.net/wzz953200463/article/details/131258170