一个非常流行的模型导入库是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);
}
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实例。
const aiScene *ReadFile(const char *pFile, unsigned int pFlags)
读取给定文件,如果成功则返回文件内容。
如果调用成功,文件的内容将作为指向一个aiScene对象的指针返回。返回的数据是只读的,导入器对象保留数据的所有权,并将在销毁时销毁数据。如果导入失败,返回nullptr。可以通过调用GetErrorString()来检索人类可读的错误描述。之前的场景将在这次调用中被删除。
return:
指向导入数据的指针,如果导入失败则为nullptr。指向场景的指针仍然属于Importer实例。使用GetOrphanedScene()来获得它的所有权。
Note:
Assimp能够自动确定文件的文件格式。
Parameters:
pFile:待导入文件的路径和文件名。
pFlags:导入成功后可选执行的后期处理步骤。提供aiPostProcessSteps标志的按位组合。如果你希望首先检查导入的场景以调整你的后处理设置,可以考虑使用ApplyPostProcessing()。
constaiScene *ApplyPostProcessing(unsigned int pFlags)
应用后处理到一个已经导入的场景。
这严格等效于调用具有相同标志的ReadFile()。但是,你可以使用这个单独的函数来检查导入的场景,以微调你的后期处理设置。
pFlags为导入成功后执行的可选后处理步骤,是一些后期处理(Post-processing)的选项,可在官网找到具体的信息。
例如:

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);
}
}


AI_SCENE_FLAGS_XXX标志的任意组合。
缺省情况下,该值为0,没有设置标志。大多数应用程序都希望拒绝所有设置了AI_SCENE_FLAGS_INCOMPLETE位的场景。
if(...||scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE||...){
...
}
表示当scene->mFlags 包含这一项时。
mRootNode,aiNode->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
相对于节点父节点的转换。
一个网格表示一个几何或模型和一个材料。
网格包含:
aiString mName)aiVector3D * mVertices)顶点数量(unsigned int mNumVertices)aiVector3D * mNormals)aiVector3D * mTangents,aiVector3D * mBitangents)aiFace * mFaces)面数量(unsigned int mNumFaces)【面组成网络】unsigned int mMaterialIndex)【一个mesh只有一个材质】aiBone ** mBones)骨骼数量(unsigned int mNumBones)aiColor4D * mColors [AI_MAX_NUMBER_OF_COLOR_SETS])aiVector3D * mTextureCoords [AI_MAX_NUMBER_OF_TEXTURECOORDS])unsigned int mNumUVComponents [AI_MAX_NUMBER_OF_TEXTURECOORDS])

材料的数据结构。
材料数据使用键值结构存储。单个键值对被称为“材料属性”。c++用户应该使用aiMaterial提供的成员函数来处理材质属性,C用户必须坚持使用aiMaterialGetXXX家族的未绑定函数。标准库定义了一组标准键(AI_MATKEY_XXX)。

获取特定纹理类型的纹理数量。
unsigned int GetTextureCount (aiTextureType type) const
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
通过type和index可以获取特定的纹理,返回值为path路径,
参数:
- mapping 纹理映射。允许NULL作为值。
- uvindex 接收纹理的UV索引。NULL是一个有效值。
- blend 接收纹理的混合因子,NULL是一个有效值。
- op 接收当前纹理和前一个纹理之间要执行的纹理操作。允许NULL作为值。
- mapmode 接收用于纹理的映射模式。参数可以是NULL,
但如果它是一个有效的指针,它必须指向一个数组的3个aiTextureMapMode
(一个为每个轴:UVW顺序(=XYZ))。
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;
}
返回的texturePath为文件名称,因此在使用QImage image=QImage(path).mirrored(false,false);时path要将根目录和文件名称相加。
类似于
QString texPath=QString("%1/%2").arg(rootPath).arg(texturePath->C_Str());
或
QImage data(directory.filePath(str.C_Str()));
获取颜色信息
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);
获取光泽度信息
scene->mMaterials[i]->Get(AI_MATKEY_SHININESS,mat.shininess);
