• QT下assimp库的模型加载


    Assimp库概述

    一个非常流行的模型导入库是Assimp,它是Open Asset Import Library(开放的资产导入库)的缩写。Assimp能够导入很多种不同的模型文件格式(并也能够导出部分的格式),它会将所有的模型数据加载至Assimp的通用数据结构中。当Assimp加载完模型之后,我们就能够从Assimp的数据结构中提取我们所需的所有数据了。由于Assimp的数据结构保持不变,不论导入的是什么种类的文件格式,它都能够将我们从这些不同的文件格式中抽象出来,用同一种方式访问我们需要的数据。

    当使用Assimp导入一个模型的时候,它通常会将整个模型加载进一个场景(Scene)对象,它会包含导入的模型/场景中的所有数据。Assimp会将场景载入为一系列的节点(Node),每个节点包含了场景对象中所储存数据的索引,每个节点都可以有任意数量的子节点。Assimp数据结构的(简化)模型如下:
    在这里插入图片描述

    加载模型

    void Model::loadModel(const QString& path)
    {
        Assimp::Importer importer;
        const aiScene *scene=importer.ReadFile(path.toLatin1(),
                                               aiProcess_Triangulate |
                                               aiProcess_GenSmoothNormals |
                                               aiProcess_FlipUVs |
                                               aiProcess_CalcTangentSpace);
        if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
        {
            qDebug() << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
            return;
        }
        int index=path.lastIndexOf('/');
        rootPath=path.left(index);
        processNode(scene->mRootNode,scene);
        processMaterials(scene);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    class Assimp::Importer

    CPP-API:Importer类形成了一个到Open Asset Import Library功能的c++接口。

    创建该类的一个对象,并调用ReadFile()来导入文件。如果导入成功,函数返回一个指向导入数据的指针。数据保留对象的属性,用于只读访问。导入的数据将与Importer对象一起被销毁。如果导入失败,ReadFile()返回一个nullptr指针。在这种情况下,您可以通过调用GetErrorString()来检索人类可读的错误描述。可以使用单个Importer实例多次调用ReadFile()。实际上,构造Importer对象涉及到相当多的分配,可能会花费一些时间,所以最好尽可能频繁地重用它们。

    如果你需要Importer做自定义文件处理来访问文件,实现IOSystem和IOStream,并在调用ReadFile()之前调用SetIOHandler()来提供一个自定义IOSystem实现的实例。如果不指定定制IO处理程序,则将使用使用标准c++ IO逻辑的默认处理程序。

    注意:
    一个Importer实例不是线程安全的。如果使用多个线程进行加载,每个线程都应该维护自己的Importer实例。

    ReadFile

    const aiScene *ReadFile(const char *pFile, unsigned int pFlags)
    
    • 1

    读取给定文件,如果成功则返回文件内容。
    如果调用成功,文件的内容将作为指向一个aiScene对象的指针返回。返回的数据是只读的,导入器对象保留数据的所有权,并将在销毁时销毁数据。如果导入失败,返回nullptr。可以通过调用GetErrorString()来检索人类可读的错误描述。之前的场景将在这次调用中被删除。
    return:
    指向导入数据的指针,如果导入失败则为nullptr。指向场景的指针仍然属于Importer实例。使用GetOrphanedScene()来获得它的所有权。
    Note:
    Assimp能够自动确定文件的文件格式。
    Parameters:
    pFile:待导入文件的路径和文件名。
    pFlags:导入成功后可选执行的后期处理步骤。提供aiPostProcessSteps标志的按位组合。如果你希望首先检查导入的场景以调整你的后处理设置,可以考虑使用ApplyPostProcessing()

    ApplyPostProcessing

    constaiScene *ApplyPostProcessing(unsigned int pFlags)
    
    • 1

    应用后处理到一个已经导入的场景。
    这严格等效于调用具有相同标志的ReadFile()。但是,你可以使用这个单独的函数来检查导入的场景,以微调你的后期处理设置。

    pFlags

    pFlags为导入成功后执行的可选后处理步骤,是一些后期处理(Post-processing)的选项,可在官网找到具体的信息。
    例如:

    • aiProcess_Triangulate,我们告诉Assimp,如果模型不是(全部)由三角形组成,它需要将模型所有的图元形状变换为三角形。
    • aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标
    • aiProcess_GenNormals:如果模型不包含法向量的话,就为每个顶点创建法线。
    • aiProcess_SplitLargeMeshes:将比较大的网格分割成更小的子网格,如果你的渲染有最大顶点数限制,只能渲染较小的网格,那么它会非常有用。
    • aiProcess_OptimizeMeshes:和上个选项相反,它会将多个小网格拼接为一个大的网格,减少绘制调用从而进行优化。

    处理导入的模型

    在这里插入图片描述

    void Model::processNode(aiNode *node,const aiScene *scene)
    {
        for(unsigned int i=0;i<node->mNumMeshes;i++)
        {
            aiMesh* mesh=scene->mMeshes[node->mMeshes[i]];
            matrialMeshesMap[mesh->mMaterialIndex].push_back(meshes.size());
            meshes.push_back(processMesh(mesh));
        }
        for(unsigned int i=0;i<node->mNumChildren;i++)
        {
            processNode(node->mChildren[i],scene);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    aiScene

    在这里插入图片描述
    在这里插入图片描述

    主要参数

    unsigned int aiScene:: mFlags

    AI_SCENE_FLAGS_XXX标志的任意组合。
    缺省情况下,该值为0,没有设置标志。大多数应用程序都希望拒绝所有设置了AI_SCENE_FLAGS_INCOMPLETE位的场景。

    if(...||scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE||...){
    ...
    }
    
    • 1
    • 2
    • 3

    表示当scene->mFlags 包含这一项时。

    struct aiNode

    mRootNodeaiNode->mChildren[i]都是aiNode类型

    共有成员

    C_STRUCT aiNode ** mChildren
    此节点的子节点。如果mNumChildren为0则为nullptr。
    unsigned int * mMeshes
    此节点的网格。
    aiString mName
    节点名称。
    unsigned int mNumChildren
    此节点的子节点数。
    unsigned int mNumMeshes
    此节点的网格数。
    aiNode * mParent
    父节点。
    aiMatrix4x4 mTransformation
    相对于节点父节点的转换。

    aiMesh** mMeshes

    一个网格表示一个几何或模型和一个材料。

    网格包含:

    • 名称(aiString mName
    • 顶点(aiVector3D * mVertices)顶点数量(unsigned int mNumVertices
    • 法线(aiVector3D * mNormals
    • 切线,双切线(aiVector3D * mTangentsaiVector3D * mBitangents
    • 面(aiFace * mFaces)面数量(unsigned int mNumFaces)【面组成网络】
    • 材质下标(unsigned int mMaterialIndex)【一个mesh只有一个材质】
    • 骨骼(aiBone ** mBones)骨骼数量(unsigned int mNumBones
    • 顶点颜色集(aiColor4D * mColors [AI_MAX_NUMBER_OF_COLOR_SETS]
    • 顶点纹理坐标,也称为UV通道。(aiVector3D * mTextureCoords [AI_MAX_NUMBER_OF_TEXTURECOORDS]
    • UV通道组件数量(unsigned int mNumUVComponents [AI_MAX_NUMBER_OF_TEXTURECOORDS]

    在这里插入图片描述
    在这里插入图片描述

    aiMaterial** mMaterials

    材料的数据结构。
    材料数据使用键值结构存储。单个键值对被称为“材料属性”。c++用户应该使用aiMaterial提供的成员函数来处理材质属性,C用户必须坚持使用aiMaterialGetXXX家族的未绑定函数。标准库定义了一组标准键(AI_MATKEY_XXX)。
    在这里插入图片描述
    获取特定纹理类型的纹理数量。

    unsigned int 	GetTextureCount (aiTextureType type) const
    
    • 1

    enum aiTextureType

    aiTextureType_NONE
    aiTextureType_DIFFUSE
    aiTextureType_SPECULAR
    aiTextureType_AMBIENT
    aiTextureType_EMISSIVE(自发光纹理),
    aiTextureType_HEIGHT(凹凸贴图),
    aiTextureType_NORMALS(法线贴图),
    aiTextureType_SHININESS (材质的光泽度),
    aiTextureType_OPACITY (像素不透明度),
    aiTextureType_DISPLACEMENT (位移贴图),
    aiTextureType_LIGHTMAP (灯光贴图纹理,又名环境遮挡),
    aiTextureType_REFLECTION 反射结构。
    包含完美的镜子反射的颜色。很少使用,几乎从不用于实时应用程序。
    aiTextureType_UNKNOWN 未知的纹理。
    一个纹理引用不符合上面的任何定义被认为是“未知的”。它仍然被导入,但是被排除在任何进一步的后处理之外。


    从材料中获得与特定纹理槽相关的所有参数。

    aiReturn GetTexture (	aiTextureType 		type, 
    						unsigned int 		index, 
    						aiString 			*path, 
    						aiTextureMapping 	*mapping=NULL, 
    						unsigned int 		*uvindex=NULL, 
    						float 				*blend=NULL, 
    						aiTextureOp 		*op=NULL, 
    						aiTextureMapMode 	*mapmode=NULL
    					) const
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过typeindex可以获取特定的纹理,返回值为path路径,
    参数:

    - mapping	纹理映射。允许NULL作为值。
    - uvindex	接收纹理的UV索引。NULL是一个有效值。
    - blend 	接收纹理的混合因子,NULL是一个有效值。
    - op 		接收当前纹理和前一个纹理之间要执行的纹理操作。允许NULL作为值。
    - mapmode	接收用于纹理的映射模式。参数可以是NULL,
    			但如果它是一个有效的指针,它必须指向一个数组的3aiTextureMapMode
    			(一个为每个轴:UVW顺序(=XYZ))
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    示例:

    texCount=scene->mMaterials[i]->GetTextureCount(aiTextureType_DIFFUSE);
    if(texCount>0)
    {
        scene->mMaterials[i]->GetTexture(aiTextureType_DIFFUSE,0,&texturePath);
        textureId=findTextureIndex(&texturePath);//findTextureIndex为自定义函数
        mat.diffuseTexture=textureId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    返回的texturePath为文件名称,因此在使用QImage image=QImage(path).mirrored(false,false);path要将根目录和文件名称相加。
    类似于

    QString texPath=QString("%1/%2").arg(rootPath).arg(texturePath->C_Str());
    
    • 1

    QImage data(directory.filePath(str.C_Str()));
    
    • 1

    获取颜色信息

    aiColor3D color;
    scene->mMaterials[i]->Get(AI_MATKEY_COLOR_AMBIENT,color);
    scene->mMaterials[i]->Get(AI_MATKEY_COLOR_DIFFUSE,color);
    scene->mMaterials[i]->Get(AI_MATKEY_COLOR_SPECULAR,color);
    
    • 1
    • 2
    • 3
    • 4

    获取光泽度信息

    scene->mMaterials[i]->Get(AI_MATKEY_SHININESS,mat.shininess);
    
    • 1

    在这里插入图片描述

  • 相关阅读:
    MVC模式简介
    采访从不写代码注释的高手,他是真的牛呀
    ESP三相SVPWM控制器的simulink仿真
    第15届蓝桥STEMA测评真题剖析-2023年10月29日Scratch编程初中级组
    关于解决 unable to start ssh-agent service, error :1058
    WMI 监控
    【从零开始学习 UVM】2.3、UVM 基础功能 —— UVM Object Copy/Clone
    K8S:K8S自动化运维容器Docker集群
    我在华为度过的 “两辈子”(学习那些在大厂表现优秀的人)
    数据结构课设:基于字符串模式匹配算法的病毒感染检测问题
  • 原文地址:https://blog.csdn.net/weixin_44518102/article/details/125801274