当前位置:网站首页>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
边栏推荐
- 【实验分享】通过Console口登录到Cisco设备
- limit 与offset的用法(转载)
- go time包常用函数
- Chisel tutorial - 04 Control flow in chisel
- AWS AWS help error
- 一个测试工程师的7年感悟 ---- 致在一路独行的你(别放弃)
- 95.(cesium篇)cesium动态单体化-3D建筑物(楼栋)
- BSS 7230 flame retardant performance test of aviation interior materials
- Reverse output three digit and arithmetic sequence
- @Detailed introduction of configuration annotation
猜你喜欢
Flash download setup
Restricted linear table
Solutions to problems in sqlserver deleting data in tables
Magic fast power
Learn about scratch
保证接口数据安全的10种方案
Benchmarking Detection Transfer Learning with Vision Transformers(2021-11)
一鍵免費翻譯300多頁的pdf文檔
[path planning] use the vertical distance limit method and Bessel to optimize the path of a star
DataGuard active / standby cleanup archive settings
随机推荐
激光slam学习(2D/3D、偏实践)
Jisuan Ke - t3104
一个测试工程师的7年感悟 ---- 致在一路独行的你(别放弃)
webflux - webclient Connect reset by peer Error
C - Fibonacci sequence again
C - minute number V3
Les mots ont été écrits, la fonction est vraiment puissante!
[summary] some panels and videos seen
gorm 关联关系小结
Download AWS toolkit pycharm
Arbre binaire équilibré [Arbre AVL] - Insérer et supprimer
mysql8.0 ubuntu20.4
平衡二叉樹【AVL樹】——插入、删除
Pycharm essential plug-in, change the background (self use, continuous update) | CSDN creation punch in
蓝桥ROS中使用fishros一键安装
aws-aws help报错
数据湖(十五):Spark与Iceberg整合写操作
Balanced binary tree [AVL tree] - insert, delete
MySQL Architecture
DataGuard active / standby cleanup archive settings