当前位置:网站首页>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
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 :
- Calculate index offset (
Mesh::indexOffset
). This will tell us where the data of the grid starts in the buffer . - Remove all vertex data from
aiMesh::mVertices[]
Copy to our vertex buffer (ModelLoader::m_vertices
). - Remove all normal data from
aiMesh::mNormals[]
Copy to our normal buffer (ModelLoader::m_normals
). - ( Optional , This tutorial does not cover ) Copy texture related data .
- Calculate the index data and add it to our index buffer (
ModelLoader::m_indices
). - Set the index count of the grid (
Mesh::indexCount
), This is the number of vertices in the mesh . - 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 :
- ( Optional ) Set the name of the node .
- Set the transformation matrix of the node .
- Copy the pointer to each mesh of the node .
- 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
- Open Asset Import Library, accessed April 30, 2014, assimp.sourceforge.net
- Phong Shading, Wikipedia article, accessed April 30, 2014, en.wikipedia.org/wiki/Phong_shading
- Assimp Material System, accessed April 30, 2014, assimp.sourceforge.net/lib_html/materials.html
- Downloadable code for this blog post :OpenGL Blog post file
About author
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
边栏推荐
- FFA与ICGA造影
- 自动化测试:Robot FrameWork框架90%的人都想知道的实用技巧
- P2141 [noip2014 popularization group] abacus mental arithmetic test
- HB 5469 combustion test method for non-metallic materials in civil aircraft cabin
- MP4文件格式解析之结合实例分析
- 数据分析系列 之3σ规则/依据拉依达准则来剔除异常值
- Arbre binaire équilibré [Arbre AVL] - Insérer et supprimer
- @Detailed introduction of configuration annotation
- Reverse output three digit and arithmetic sequence
- Kubectl 好用的命令行工具:oh-my-zsh 技巧和窍门
猜你喜欢
Install sqlserver2019
Svn relocation
35岁那年,我做了一个面临失业的决定
Ora-01741 and ora-01704
MP4文件格式解析之结合实例分析
快速回复二极管整流特性
BSS 7230 flame retardant performance test of aviation interior materials
About the difference between ch32 library function and STM32 library function
Magic fast power
P1067 [noip2009 popularity group] polynomial output (difficult, pit)
随机推荐
Reverse output three digit and arithmetic sequence
Chisel tutorial - 04 Control flow in chisel
串联二极管,提高耐压
ping报错:未知的名称或服务
数据库面试题+解析
Where are you going
自动化测试:Robot FrameWork框架90%的人都想知道的实用技巧
Postgres timestamp to human eye time string or millisecond value
Traduction gratuite en un clic de plus de 300 pages de documents PDF
Chisel tutorial - 01 Introduction to Scala
Introduction to programming hardware
AWS AWS help error
Installing gradle
At the age of 35, I made a decision to face unemployment
Kubectl 好用的命令行工具:oh-my-zsh 技巧和窍门
go time包常用函数
Pycharm essential plug-in, change the background (self use, continuous update) | CSDN creation punch in
postgis学习
网上买基金安全么?
BSS 7230 flame retardant performance test of aviation interior materials