assimp 介绍
assimp 是一个用来导入和处理 3D 模型数据的库。它支持多种格式的 3D 模型,比如 obj、fbx 和 glb 等等。
1. 编译
assimp 的仓库地址为 https://github.com/assimp/assimp。我们克隆后,使用 CMake 进行编译。
CMake 的设置如图 1 所示,我们设置源代码路径和编译结果路径。此外还在编译选项中,指定了安装路径(CMAKE_INSTALL_PREFIX)。

设置好后,依次点击 Configure、Generate 按钮。然后点击 Open Project 按钮,打开 Visual Studio 工程,编译 INSTALL 目标即可。
在之前指定的安装路径下,我们可以得到最终想要的产物:bin 目录下是动态链接库;include 目录下是头文件;lib 目录下是导入库。
- ├─bin
- ├─include
- │ └─assimp
- │ └─Compiler
- └─lib
- ├─cmake
- │ └─assimp-5.4
- └─pkgconfig
使用的话,以 Visual Studio 举例:在项目属性页的 VC++ 目录/包含目录 里指定头文件目录;在 VC++ 目录/库目录 里指定导入库目录;在 链接器/输入/附加依赖项 里指定具体的导入库名称。
动态链接库通常放在和执行文件相同的目录,我们可以在 生成事件/生成前事件/命令行 中书写复制命令。
- xcopy /Y /D "$(ProjectDir)assimp\bin\*" "$(OutDir)"
2. 使用
我们首先使用简单的小模型,来理解 assimp 内部的数据结构和处理逻辑。
如图 2 所示,我在 Maya 中创建了三个立方体。另外两个是复制得到的。同时,我设置了纹理图片。
注意看左侧大纲视图里面的结构。立方体 1 在根节点下;一个复制的立方体在一个 group 下;另一个复制的立方体在两级 group 下。

assimp 对模型的组织逻辑也是类似这种层级结构。assimp 中的 aiNode,可以简单对应 maya 中的 group;aiMesh 可以简单对应 maya 中的几何体信息集合。
我们再结合实际代码来理解一下。代码清单 1 是 assimp 最基础的解析流程。加载模型后,会得到一个根节点 aiNode。一个 aiNode 下,会有多个 aiNode 和 aiMesh。由此依次递归遍历。
- void ProcessMesh(aiMesh* mesh, const aiScene* scene)
- {
- std::cout << "Mesh: " << mesh->mName.C_Str() << std::endl;
- std::cout << "Vertices count: " << mesh->mNumVertices << std::endl;
- }
- void ProcessNode(aiNode* node, const aiScene* scene)
- {
- for (unsigned int i = 0; i < node->mNumMeshes; i++)
- {
- aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
- ProcessMesh(mesh, scene);
- }
- for (unsigned int i = 0; i < node->mNumChildren; i++)
- {
- ProcessNode(node->mChildren[i], scene);
- }
- }
- void LoadModel(const std::string& path)
- {
- Assimp::Importer importer;
- const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
- if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
- std::cerr << "Error: " << importer.GetErrorString() << std::endl;
- return;
- }
- ProcessNode(scene->mRootNode, scene);
- }
- int main()
- {
- LoadModel("test.fbx");
- }
代码清单 1 的输出如下,可以看到 mesh 的名字和 maya 中几何体的名字是对应的。
- Mesh: pCube1
- Vertices count: 24
- Mesh: pasted__pCube1
- Vertices count: 24
- Mesh: pasted__pasted__pCube1
- Vertices count: 24
如代码清单 2 所示,我们可以在 aiMesh 中获取到顶点数据。
- void ProcessMesh(aiMesh* mesh, const aiScene* scene)
- {
- std::cout << "Mesh: " << mesh->mName.C_Str() << std::endl;
- std::cout << "Vertices count: " << mesh->mNumVertices << std::endl;
- for (unsigned int i = 0; i < mesh->mNumVertices; i++)
- {
- aiVector3D vertex = mesh->mVertices[i];
- std::cout << "Vertex " << i + 1 << ": ("
- << vertex.x << ", "
- << vertex.y << ", "
- << vertex.z << ")" << std::endl;
- }
- }
从输出结果中,我们会注意到一个问题:三个立方体的顶点坐标都是一样的,但是在图 2 的视图中,三个立方体是有位置偏移的。
- Mesh: pCube1
- Vertices count: 24
- Vertex 1: (-0.5, -0.5, 0.5)
- Vertex 2: (0.5, -0.5, 0.5)
- Vertex 3: (0.5, 0.5, 0.5)
- Vertex 4: (-0.5, 0.5, 0.5)
- Vertex 5: (-0.5, 0.5, 0.5)
- Vertex 6: (0.5, 0.5, 0.5)
- Vertex 7: (0.5, 0.5, -0.5)
- Vertex 8: (-0.5, 0.5, -0.5)
- Vertex 9: (-0.5, 0.5, -0.5)
- Vertex 10: (0.5, 0.5, -0.5)
- Vertex 11: (0.5, -0.5, -0.5)
- Vertex 12: (-0.5, -0.5, -0.5)
- Vertex 13: (-0.5, -0.5, -0.5)
- Vertex 14: (0.5, -0.5, -0.5)
- Vertex 15: (0.5, -0.5, 0.5)
- Vertex 16: (-0.5, -0.5, 0.5)
- Vertex 17: (0.5, -0.5, 0.5)
- Vertex 18: (0.5, -0.5, -0.5)
- Vertex 19: (0.5, 0.5, -0.5)
- Vertex 20: (0.5, 0.5, 0.5)
- Vertex 21: (-0.5, -0.5, -0.5)
- Vertex 22: (-0.5, -0.5, 0.5)
- Vertex 23: (-0.5, 0.5, 0.5)
- Vertex 24: (-0.5, 0.5, -0.5)
- Mesh: pasted__pCube1
- Vertices count: 24
- Vertex 1: (-0.5, -0.5, 0.5)
- Vertex 2: (0.5, -0.5, 0.5)
- Vertex 3: (0.5, 0.5, 0.5)
- Vertex 4: (-0.5, 0.5, 0.5)
- Vertex 5: (-0.5, 0.5, 0.5)
- Vertex 6: (0.5, 0.5, 0.5)
- Vertex 7: (0.5, 0.5, -0.5)
- Vertex 8: (-0.5, 0.5, -0.5)
- Vertex 9: (-0.5, 0.5, -0.5)
- Vertex 10: (0.5, 0.5, -0.5)
- Vertex 11: (0.5, -0.5, -0.5)
- Vertex 12: (-0.5, -0.5, -0.5)
- Vertex 13: (-0.5, -0.5, -0.5)
- Vertex 14: (0.5, -0.5, -0.5)
- Vertex 15: (0.5, -0.5, 0.5)
- Vertex 16: (-0.5, -0.5, 0.5)
- Vertex 17: (0.5, -0.5, 0.5)
- Vertex 18: (0.5, -0.5, -0.5)
- Vertex 19: (0.5, 0.5, -0.5)
- Vertex 20: (0.5, 0.5, 0.5)
- Vertex 21: (-0.5, -0.5, -0.5)
- Vertex 22: (-0.5, -0.5, 0.5)
- Vertex 23: (-0.5, 0.5, 0.5)
- Vertex 24: (-0.5, 0.5, -0.5)
- Mesh: pasted__pasted__pCube1
- Vertices count: 24
- Vertex 1: (-0.5, -0.5, 0.5)
- Vertex 2: (0.5, -0.5, 0.5)
- Vertex 3: (0.5, 0.5, 0.5)
- Vertex 4: (-0.5, 0.5, 0.5)
- Vertex 5: (-0.5, 0.5, 0.5)
- Vertex 6: (0.5, 0.5, 0.5)
- Vertex 7: (0.5, 0.5, -0.5)
- Vertex 8: (-0.5, 0.5, -0.5)
- Vertex 9: (-0.5, 0.5, -0.5)
- Vertex 10: (0.5, 0.5, -0.5)
- Vertex 11: (0.5, -0.5, -0.5)
- Vertex 12: (-0.5, -0.5, -0.5)
- Vertex 13: (-0.5, -0.5, -0.5)
- Vertex 14: (0.5, -0.5, -0.5)
- Vertex 15: (0.5, -0.5, 0.5)
- Vertex 16: (-0.5, -0.5, 0.5)
- Vertex 17: (0.5, -0.5, 0.5)
- Vertex 18: (0.5, -0.5, -0.5)
- Vertex 19: (0.5, 0.5, -0.5)
- Vertex 20: (0.5, 0.5, 0.5)
- Vertex 21: (-0.5, -0.5, -0.5)
- Vertex 22: (-0.5, -0.5, 0.5)
- Vertex 23: (-0.5, 0.5, 0.5)
- Vertex 24: (-0.5, 0.5, -0.5)
这就引入了 aiNode 中变换矩阵的概念。和 maya 中 group 的概念类似。在 maya 中,选中 group 进行移动或旋转等操作,操作会作用于 group 中的所有物体。aiNode 中变换矩阵也需要作用于其下的所有子内容。
如代码清单 3 所示,我们增加 aiNode 的变换矩阵的打印。
可以试一下,顶点坐标作用上变换矩阵。结果可以在 Maya 的 transform 信息中进行核对。可以验证的确是这样的。
- void PrintMatrix(const aiMatrix4x4& matrix)
- {
- std::cout << "Transformation Matrix:" << std::endl;
- for (int i = 0; i < 4; i++)
- {
- for (int j = 0; j < 4; j++)
- {
- std::cout << matrix[i][j] << " ";
- }
- std::cout << std::endl;
- }
- }
- void ProcessNode(aiNode* node, const aiScene* scene)
- {
- PrintMatrix(node->mTransformation);
- for (unsigned int i = 0; i < node->mNumMeshes; i++)
- {
- aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
- ProcessMesh(mesh, scene);
- }
- for (unsigned int i = 0; i < node->mNumChildren; i++)
- {
- ProcessNode(node->mChildren[i], scene);
- }
- }
最后,如代码清单 4 所示,我们尝试在 aiMesh 中获取纹理信息。我们打印纹理图片的路径。
- void PrintMaterialTextures(aiMaterial* material, aiTextureType type, const std::string& typeName)
- {
- for (unsigned int i = 0; i < material->GetTextureCount(type); i++)
- {
- aiString str;
- material->GetTexture(type, i, &str);
- std::string texturePath = str.C_Str();
- std::cout << typeName << " Texture " << i + 1 << ": " << texturePath << std::endl;
- }
- }
- void ProcessMesh(aiMesh* mesh, const aiScene* scene)
- {
- std::cout << "Mesh: " << mesh->mName.C_Str() << std::endl;
- std::cout << "Vertices count: " << mesh->mNumVertices << std::endl;
- for (unsigned int i = 0; i < mesh->mNumVertices; i++)
- {
- aiVector3D vertex = mesh->mVertices[i];
- std::cout << "Vertex " << i + 1 << ": ("
- << vertex.x << ", "
- << vertex.y << ", "
- << vertex.z << ")" << std::endl;
- }
- if (mesh->mMaterialIndex >= 0)
- {
- aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
- PrintMaterialTextures(material, aiTextureType_DIFFUSE, "Diffuse");
- }
- }
可以通过打印信息验证,的确是我在 Maya 中设置的图片名字。
- Diffuse Texture 1: ..\1.gif
在这篇文章中,我们了解了 assimp 的编译和使用。也对 assimp 的 aiNode、aiMesh 组织结构有了基本的理解。具体的模型读取细节,以及模型绘制实现,会在下一篇文章中进行讲解。