assimp 介绍

assimp 是一个用来导入和处理 3D 模型数据的库。它支持多种格式的 3D 模型,比如 obj、fbx 和 glb 等等。

1. 编译

assimp 的仓库地址为 https://github.com/assimp/assimp。我们克隆后,使用 CMake 进行编译。

CMake 的设置如图 1 所示,我们设置源代码路径和编译结果路径。此外还在编译选项中,指定了安装路径(CMAKE_INSTALL_PREFIX)。

图1 CMake 设置

设置好后,依次点击 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 下。

图2 模型

assimp 对模型的组织逻辑也是类似这种层级结构。assimp 中的 aiNode,可以简单对应 maya 中的 group;aiMesh 可以简单对应 maya 中的几何体信息集合。

我们再结合实际代码来理解一下。代码清单 1 是 assimp 最基础的解析流程。加载模型后,会得到一个根节点 aiNode。一个 aiNode 下,会有多个 aiNode 和 aiMesh。由此依次递归遍历。

代码清单 1 遍历
  1. void ProcessMesh(aiMesh* mesh, const aiScene* scene)
  2. {
  3.     std::cout << "Mesh: " << mesh->mName.C_Str() << std::endl;
  4.     std::cout << "Vertices count: " << mesh->mNumVertices << std::endl;
  5. }
  6.  
  7. void ProcessNode(aiNode* node, const aiScene* scene)
  8. {
  9.     for (unsigned int i = 0; i < node->mNumMeshes; i++)
  10.     {
  11.         aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
  12.         ProcessMesh(mesh, scene);
  13.     }
  14.  
  15.     for (unsigned int i = 0; i < node->mNumChildren; i++)
  16.     {
  17.         ProcessNode(node->mChildren[i], scene);
  18.     }
  19. }
  20.  
  21. void LoadModel(const std::string& path)
  22. {
  23.     Assimp::Importer importer;
  24.     const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
  25.  
  26.     if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
  27.         std::cerr << "Error: " << importer.GetErrorString() << std::endl;
  28.         return;
  29.     }
  30.  
  31.     ProcessNode(scene->mRootNode, scene);
  32. }
  33.  
  34. int main()
  35. {
  36.     LoadModel("test.fbx");
  37. }

代码清单 1 的输出如下,可以看到 mesh 的名字和 maya 中几何体的名字是对应的。

  • Mesh: pCube1
  • Vertices count: 24
  • Mesh: pasted__pCube1
  • Vertices count: 24
  • Mesh: pasted__pasted__pCube1
  • Vertices count: 24

如代码清单 2 所示,我们可以在 aiMesh 中获取到顶点数据。

代码清单 2 顶点
  1. void ProcessMesh(aiMesh* mesh, const aiScene* scene)
  2. {
  3.     std::cout << "Mesh: " << mesh->mName.C_Str() << std::endl;
  4.     std::cout << "Vertices count: " << mesh->mNumVertices << std::endl;
  5.  
  6.     for (unsigned int i = 0; i < mesh->mNumVertices; i++)
  7.     {
  8.         aiVector3D vertex = mesh->mVertices[i];
  9.         std::cout << "Vertex " << i + 1 << ": ("
  10.             << vertex.x << ", "
  11.             << vertex.y << ", "
  12.             << vertex.z << ")" << std::endl;
  13.     }
  14. }

从输出结果中,我们会注意到一个问题:三个立方体的顶点坐标都是一样的,但是在图 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 信息中进行核对。可以验证的确是这样的。

代码清单 3 变换矩阵
  1. void PrintMatrix(const aiMatrix4x4& matrix)
  2. {
  3.     std::cout << "Transformation Matrix:" << std::endl;
  4.     for (int i = 0; i < 4; i++)
  5.     {
  6.         for (int j = 0; j < 4; j++)
  7.         {
  8.             std::cout << matrix[i][j] << " ";
  9.         }
  10.         std::cout << std::endl;
  11.     }
  12. }
  13.  
  14. void ProcessNode(aiNode* node, const aiScene* scene)
  15. {
  16.     PrintMatrix(node->mTransformation);
  17.  
  18.     for (unsigned int i = 0; i < node->mNumMeshes; i++)
  19.     {
  20.         aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
  21.         ProcessMesh(mesh, scene);
  22.     }
  23.  
  24.     for (unsigned int i = 0; i < node->mNumChildren; i++)
  25.     {
  26.         ProcessNode(node->mChildren[i], scene);
  27.     }
  28. }

最后,如代码清单 4 所示,我们尝试在 aiMesh 中获取纹理信息。我们打印纹理图片的路径。

代码清单 4 纹理信息
  1. void PrintMaterialTextures(aiMaterial* material, aiTextureType type, const std::string& typeName)
  2. {
  3.     for (unsigned int i = 0; i < material->GetTextureCount(type); i++)
  4.     {
  5.         aiString str;
  6.         material->GetTexture(type, i, &str);
  7.  
  8.         std::string texturePath = str.C_Str();
  9.         std::cout << typeName << " Texture " << i + 1 << ": " << texturePath << std::endl;   
  10.     }
  11. }
  12.  
  13. void ProcessMesh(aiMesh* mesh, const aiScene* scene)
  14. {
  15.     std::cout << "Mesh: " << mesh->mName.C_Str() << std::endl;
  16.     std::cout << "Vertices count: " << mesh->mNumVertices << std::endl;
  17.  
  18.     for (unsigned int i = 0; i < mesh->mNumVertices; i++)
  19.     {
  20.         aiVector3D vertex = mesh->mVertices[i];
  21.         std::cout << "Vertex " << i + 1 << ": ("
  22.             << vertex.x << ", "
  23.             << vertex.y << ", "
  24.             << vertex.z << ")" << std::endl;
  25.     }
  26.  
  27.     if (mesh->mMaterialIndex >= 0)
  28.     {
  29.         aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
  30.  
  31.         PrintMaterialTextures(material, aiTextureType_DIFFUSE, "Diffuse");
  32.     }
  33. }

可以通过打印信息验证,的确是我在 Maya 中设置的图片名字。

  • Diffuse Texture 1: ..\1.gif

在这篇文章中,我们了解了 assimp 的编译和使用。也对 assimp 的 aiNode、aiMesh 组织结构有了基本的理解。具体的模型读取细节,以及模型绘制实现,会在下一篇文章中进行讲解。