当前位置:网站首页>QT and OpenGL: load 3D models using the open asset import library (assimp)

QT and OpenGL: load 3D models using the open asset import library (assimp)

2022-07-07 23:53:00 Raring_ Ringtail

Qt and OpenGL: Use Open Asset Import Library(ASSIMP) load 3D Model

Translated from :https://www.ics.com/blog/qt-and-opengl-loading-3d-model-open-asset-import-library-assimp

By Eric Stone Wednesday, May 21, 2014

TwitterLinkedInFacebookReddit

This blog post is the first in the series , This series of articles will show you how to OpenGL And Qt Use it together . In this issue , We will study how to use Open Asset Import Library(ASSIMP)(1) From some common 3D Model format loading 3D Model . This sample code requires Assimp 3.0 Above version . The code will also use Qt Multiple convenience classes of (QString,QVector,QSharedPointer etc. ).

Read page 2 part Qt and OpenGL: Use Open Asset Import Library(ASSIMP) load 3D Model

Introduce

First , We will create some simple classes to store the data of the model . structure MaterialInfo Will contain information about the appearance of the material . We will use Phong shading model (2) Coloring .

struct MaterialInfo
{
    
    QString Name;
    QVector3D Ambient;
    QVector3D Diffuse;
    QVector3D Specular;
    float Shininess;
};

LightInfo The structure will contain information about the light source :

struct LightInfo
{
    
    QVector4D Position;
    QVector3D Intensity;
};

Mesh Class will provide us with information about the grid . It does not actually contain vertex data of the mesh , But it has the information we need to get from the vertex buffer . Mesh::indexCount Is the number of vertices in the mesh ,Mesh::indexOffset Is the starting position of vertex data in the buffer ,Mesh::material Is the material information of the mesh .

struct Mesh
{
    
    QString name;
    unsigned int indexCount;
    unsigned int indexOffset;
    QSharedPointer<MaterialInfo> material;
};

A single model may have many different meshes . Node Class will contain the mesh and the transformation matrix that places it in the scene . Each node can also have child nodes . We can store all the meshes in a single array , But storing them in a tree structure makes it easier to animate objects . It can be regarded as a human body , It's like the body is the root node , The upper arm will be a child of the root node , The lower arm will be a child of the upper arm node , The hand will be the child node of the lower arm node .

struct Node
{
    
    QString name;
    QMatrix4x4 transformation;
    QVector<QSharedPointer<Mesh> > meshes;
    QVector<Node> nodes;
};

ModelLoader Class will be used to load information into a single root node :

class ModelLoader
{
    
public:
    ModelLoader();
    bool Load(QString pathToFile);
    void getBufferData(QVector<float> **vertices, QVector<float> **normals,
                        QVector<unsigned int> **indices);

    QSharedPointer<Node> getNodeData() {
     return m_rootNode; }

The usage of this class will be simple . ModelLoader::Load() Accept 3D Path to model file , And load the model . ModelLoader::getBufferData() Used to retrieve the vertex position of the indexed graph 、 Normals and indexes . ModelLoader::getNodeData() The root node will be returned .

Here is ModelLoader Private functions and variables :

    QSharedPointer<MaterialInfo> processMaterial(aiMaterial *mater);
    QSharedPointer<Mesh> processMesh(aiMesh *mesh);
    void processNode(const aiScene *scene, aiNode *node, Node *parentNode, Node &newNode);

    void transformToUnitCoordinates();
    void findObjectDimensions(Node *node, QMatrix4x4 transformation, QVector3D &minDimension, QVector3D &maxDimension);

    QVector<float> m_vertices;
    QVector<float> m_normals;
    QVector<unsigned int> m_indices;

    QVector<QSharedPointer<MaterialInfo> > m_materials;
    QVector<QSharedPointer<Mesh> > m_meshes;
    QSharedPointer<Node> m_rootNode;

The next step is to load the model . If not installed Assimp 3.0, Must be installed Assimp 3.0. Please note that ,Assimp 2.0 Does not work in this example . First , We include the necessary Assimp header :

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

This is a ModelLoader::Load() Function code :

bool ModelLoader::Load(QString pathToFile)
{
    
    Assimp::Importer importer;

    const aiScene* scene = importer.ReadFile(pathToFile.toStdString(),
            aiProcess_GenSmoothNormals |
            aiProcess_CalcTangentSpace |
            aiProcess_Triangulate |
            aiProcess_JoinIdenticalVertices |
            aiProcess_SortByPType
            );

    if (!scene)
    {
    
        qDebug() << "Error loading file: (assimp:) " << importer.GetErrorString();
        return false;
    }

Assimp Store all the information about the model here aiScene In the example . importer The object is preserved aiScene Ownership of objects , So we don't have to worry about it being deleted in the future . If an error occurs , The returned scene object will be null, So we check here , If an error occurs , Returns... From the function false.

About passing to importer More details about the logo of , Please see the Assimp Of postprocess.h file . The following is the introduction of the logo mentioned above :

  • GenSmoothNormals: If there is no normal in the model , Generate normal .
  • CalcTangentSpace: Calculate tangent space , It is only necessary for normal mapping .
  • Triangulate: Split an entity with more than three vertices into triangles .
  • JoinIdenticalVertices: Connect the same vertex data , And improve performance by indexing graphics .

If scene Not for null, Then we can assume that the model has loaded correctly and started copying the required data . The data will be read in the following order :

1. texture of material (Materials)
2. grid (Meshes)
3. node (Nodes)

The material must be loaded before the mesh , The mesh must be loaded before the node .

Load material

The next step is to load materials :

    if (scene->HasMaterials())
    {
    
        for (unsigned int ii = 0; ii < scene->mNumMaterials; ++ii)
        {
    
            QSharedPointer<MaterialInfo> mater = processMaterial(scene->mMaterials[ii]);
            m_materials.push_back(mater);
        }
    }

All materials are stored in aiScene::mMaterials Array , And the array size is aiScene::nNumMaterials. We traverse each object , And pass it on to our processMaterial function , This function will return a new MaterialInfo object . then , Variable m_materials It will contain the material information of all meshes in the scene ( If available ).

Let's take a closer look at what we will use ModelLoader::processMaterial Realization :

QSharedPointer<MaterialInfo> ModelLoader::processMaterial(aiMaterial *material)
{
    
    QSharedPointer<MaterialInfo> mater(new MaterialInfo);

    aiString mname;
    material->Get(AI_MATKEY_NAME, mname);
    if (mname.length > 0)
        mater->Name = mname.C_Str();

    int shadingModel;
    material->Get(AI_MATKEY_SHADING_MODEL, shadingModel);

    if (shadingModel != aiShadingMode_Phong && shadingModel != aiShadingMode_Gouraud)
    {
    
        qDebug() << "This mesh's shading model is not implemented in this loader, setting to default material";
        mater->Name = "DefaultMaterial";
    }
    else
        ...

aiMaterial Class uses key value pairs to store material data . We copy the name , Then check the lighting model of this material . In this tutorial , We just need to focus on Phong or Gouraud shading model , therefore , If not one of them , Then set the name to “DefaultMaterial” To indicate that rendering should use its own material values .

Continue with the above code :

    ...
    }
    else
    {
    
        aiColor3D dif(0.f,0.f,0.f);
        aiColor3D amb(0.f,0.f,0.f);
        aiColor3D spec(0.f,0.f,0.f);
        float shine = 0.0;

        material->Get(AI_MATKEY_COLOR_AMBIENT, amb);
        material->Get(AI_MATKEY_COLOR_DIFFUSE, dif);
        material->Get(AI_MATKEY_COLOR_SPECULAR, spec);
        material->Get(AI_MATKEY_SHININESS, shine);

        mater->Ambient = QVector3D(amb.r, amb.g, amb.b);
        mater->Diffuse = QVector3D(dif.r, dif.g, dif.b);
        mater->Specular = QVector3D(spec.r, spec.g, spec.b);
        mater->Shininess = shine;

        mater->Ambient *= .2;
        if (mater->Shininess == 0.0)
            mater->Shininess = 30;
    }

    return mater;
}

We only deal with the ambient light , Diffuse reflection , Specular reflection and gloss are very sexy . You can go to here (3) See a longer list of available attributes . call aiMaterial::Get(key, value) Get the desired value , Then copy it to MaterialInfo object .

Please note that , We have reduced the ambient light value here . This is because we use for rendering OpenGL Shaders are only for ambient lighting , Diffuse and specular incident light use the same illumination intensity vector (LightInfo::Intensity). in addition , Our shader can illuminate the environment of the light source , Diffuse and specular components use separate vectors , To achieve better control . We also check whether the brightness value is specified for the model , without , The default value is set to 30.

Load grid

go back to Load() Function :

    if (scene->HasMeshes())
    {
    
        for (unsigned int ii = 0; ii < scene->mNumMeshes; ++ii)
        {
    
            m_meshes.push_back(processMesh(scene->mMeshes[ii]));
        }
    }
    else
    {
    
        qDebug() << "Error: No meshes found";
        return false;
    }

All meshes are stored in aiScene::mMeshes Array , And the array size is aiScene::nNumMeshes. We traverse each object , And pass it on to our ModelLoader::processMesh function , This function will return a new Mesh object . Variable m_meshes All meshes in the scene will be included .

here , Each mesh will be associated with a material . If no material is assigned in the model , It will have the default material , Its MaterialInfo::Name Set to DefaultMaterial. To load the mesh , We need to do the following :

  1. Calculate index offset (Mesh::indexOffset). This will tell us where the data of the grid starts in the buffer .
  2. Remove all vertex data from aiMesh::mVertices[] Copy to our vertex buffer (ModelLoader::m_vertices).
  3. Remove all normal data from aiMesh::mNormals[] Copy to our normal buffer (ModelLoader::m_normals).
  4. ( Optional , This tutorial does not cover ) Copy texture related data .
  5. Calculate the index data and add it to our index buffer (ModelLoader::m_indices).
  6. Set the index count of the grid (Mesh::indexCount), This is the number of vertices in the mesh .
  7. Set the material of the mesh (Mesh::material).
QSharedPointer<Mesh> ModelLoader::processMesh(aiMesh *mesh)
{
    
    QSharedPointer<Mesh> newMesh(new Mesh);
    newMesh->name = mesh->mName.length != 0 ? mesh->mName.C_Str() : "";
    newMesh->indexOffset = m_indices.size();
    unsigned int indexCountBefore = m_indices.size();
    int vertindexoffset = m_vertices.size()/3;

    // Get Vertices
    if (mesh->mNumVertices > 0)
    {
    
        for (uint ii = 0; ii < mesh->mNumVertices; ++ii)
        {
    
            aiVector3D &vec = mesh->mVertices[ii];

            m_vertices.push_back(vec.x);
            m_vertices.push_back(vec.y);
            m_vertices.push_back(vec.z);
        }
    }

    // Get Normals
    if (mesh->HasNormals())
    {
    
        for (uint ii = 0; ii < mesh->mNumVertices; ++ii)
        {
    
            aiVector3D &vec = mesh->mNormals[ii];
            m_normals.push_back(vec.x);
            m_normals.push_back(vec.y);
            m_normals.push_back(vec.z);
        };
    }

    // Get mesh indexes
    for (uint t = 0; t < mesh->mNumFaces; ++t)
    {
    
        aiFace* face = &mesh->mFaces[t];
        if (face->mNumIndices != 3)
        {
    
            qDebug() << "Warning: Mesh face with not exactly 3 indices, ignoring this primitive.";
            continue;
        }

        m_indices.push_back(face->mIndices[0]+vertindexoffset);
        m_indices.push_back(face->mIndices[1]+vertindexoffset);
        m_indices.push_back(face->mIndices[2]+vertindexoffset);
    }

    newMesh->indexCount = m_indices.size() - indexCountBefore;
    newMesh->material = m_materials.at(mesh->mMaterialIndex);

    return newMesh;
}

Most of them are simple . Because only one buffer is used for each vertex (Assimp Each grid has a buffer ), Therefore, you need to add the offset to the index value .

aiMesh Store index data in aiFace Object array . aiFace Represents a basic drawing figure . If face The number of indexes of is not equal to 3, Then it is not a triangle , So we will ignore it in this tutorial .

If face It's a triangle , Then add the index value to m_indices. Remember to add vertex offset values to it , because Assimp The index given is relative to the grid , We store the indexes of all grids in a buffer .

Since we have processed all indexes of the grid , So now we can calculate the index count of the grid , And set the material of the mesh .

In this tutorial , We only focus on vertices , Normals and indexes , But you can load other information here , For example, vertex texture coordinates or tangents . Downloadable sample code (4) It also contains functions to obtain this information .

Load node

Next , We must start from the root node aiScene The nodes in the . Nodes define where the mesh is drawn relative to each other . Make sure aiScene The root node of is not null, Then pass it to processNode(), This will enable filling with all model data ModelLoader::m_rootNode.

    if (scene->mRootNode != NULL)
    {
    
        Node *rootNode = new Node;
        processNode(scene, scene->mRootNode, 0, *rootNode);
        m_rootNode.reset(rootNode);
    }
    else
    {
    
        qDebug() << "Error loading model";
        return false;
    }

    return true;
}

This is a processNode Implementation steps . We need to perform the following steps :

  1. ( Optional ) Set the name of the node .
  2. Set the transformation matrix of the node .
  3. Copy the pointer to each mesh of the node .
  4. Add child nodes , And call... For each child node ModelLoader::processNode. This will recursively process all children .
void ModelLoader::processNode(const aiScene *scene, aiNode *node, Node *parentNode, Node &newNode)
{
    
    newNode.name = node->mName.length != 0 ? node->mName.C_Str() : "";

    newNode.transformation = QMatrix4x4(node->mTransformation[0]);

    newNode.meshes.resize(node->mNumMeshes);
    for (uint imesh = 0; imesh < node->mNumMeshes; ++imesh)
    {
    
        QSharedPointer<Mesh> mesh = m_meshes[node->mMeshes[imesh]];
        newNode.meshes[imesh] = mesh;
    }

    for (uint ich = 0; ich < node->mNumChildren; ++ich)
    {
    
        newNode.nodes.push_back(Node());
        processNode(scene, node->mChildren[ich], parentNode, newNode.nodes[ich]);
    }
}

Finishing work

This class can be used as follows :

    ModelLoader model;

    if (!model.Load("head.3ds"))
    {
    
        m_error = true;
        return;
    }

    QVector<float> *vertices;
    QVector<float> *normals;
    QVector<unsigned int> *indices;

    model.getBufferData(&vertices, &normals, &indices);

    m_rootNode = model.getNodeData();

thus , You have used OpenGL Display all the data required by the model .

Downloadable examples (4) Full source code for , Include qmake Project documents . If there is Assimp 3 The above Library , Then it can run on any platform . You may need to adjust the path in the project file . In the latest version of Linux( for example Ubuntu) On , Appropriate version Assimp Can be used as Linux Part of the distribution provides . stay Mac and Windows On , You may need to build from source Assimp. There are two groups of shaders and scene classes , One set for OpenGL 3.3, The other group is for OpenGL 2.1 / OpenGL ES2. It will try to run 3.3 edition , But if necessary, it should automatically fall back to GL 2 edition .

summary

This blog post demonstrates how to use Qt and Assimp Library loading 3D Model .

Read page 2 part Qt and OpenGL: Use Open Asset Import Library(ASSIMP) load 3D Model

reference

  1. Open Asset Import Library, accessed April 30, 2014, assimp.sourceforge.net
  2. Phong Shading, Wikipedia article, accessed April 30, 2014, en.wikipedia.org/wiki/Phong_shading
  3. Assimp Material System, accessed April 30, 2014, assimp.sourceforge.net/lib_html/materials.html
  4. Downloadable code for this blog post :OpenGL Blog post file

About author

Eric Stone

Eric Stone

Eric yes ICS Software Engineer of , With the use of C++ Rich experience in programming . He has used it Qt and OpenGL Programming for more than six years , And has practical experience in developing applications on desktop computers and embedded devices .

original text :https://www.ics.com/blog/qt-and-opengl-loading-3d-model-open-asset-import-library-assimp


Welcome to my official account. Notes on Jiangda

原网站

版权声明
本文为[Raring_ Ringtail]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202130554126094.html