Assimp
序言
Github
一个非常流行的模型导入库 Assimp(Open Asset Import Library)。它能够导入很多种不同的模型文件格式(也能导出一些),并在内存中呈现为 Assimp 的通用数据结构。一旦 Assimp 加载了模型,我们就可以通过 Assimp 提供的接口检索需要的数据。
编译
Assimp 官方比较推荐 vcpkg 的集成方式。
手动集成的话有三个坑:
- config.h
假设生成目录是assimp/build
,Assimp 会在assimp/build/include/assimp
下生成一份config.h
,这个文件最终会被 include 进源码里。
所以在我们的项目中不但要包含assimp/include
路径,还要包含assimp/build/include/assimp
路径。 - zlib
Assimp 依赖于 zlib 运行,所以在我们的项目中不但要链接assimp-vc143-mt
库还要包含zlibstatic
库,这个项目生成在assimp/build/contrib/zlib
下,目标文件生成在assimp/build/contrib/zlib/Debug(or Release)
下。 - stb
Assimp 默认包含一份 stb_image 的定义,如果你的项目中需要手动包含 stb_image,其中一份实现会被忽略。
Assimp 默认会 defineSTBI_ONLY_PNG
,导致 stb_image 只能读取 .png 格式。
这块的逻辑可以看下 Assimp.cpp 最后几行。
解决办法是在 make Assimp 的时候使用-DASSIMP_NO_EXPORT=ON
。
官方文档 assimp/Build.md 似乎没有提及这几件事,很坑。
导入
调用 Assimp::Importer
之后 Assimp 解析的模型数据会以一个只读的 aiScene *
的形式返回,并且当 Importer 实例销毁之后对应的 aiScene 也会销毁。
1 |
|
ReadFile
的 aiProcess_XXX 是一个比较重要的参数,是 Assimp 提供的对模型的后处理,比如 aiProcess_GenNormals
代表如果模型不包含法向量的话由 Assimp 生成法向量。更多的选项看:postprocess.h File Reference。
规范
默认:
- 右手坐标系
- CCW
- UV 原点位于左下角
矩阵有一点奇怪,左乘并且以行主序存储,例如:
在内存中的排列是:[X1, Y1, Z1, T1, X2, Y2, Z2, T2, X3, Y3, Z3, T3, 0, 0, 0, 1]
。
以上规范大都可以通过 Post Process 进行修改。
比如 aiProcess_ConvertToLeftHanded
代表:
Supersedes the
aiProcess_MakeLeftHanded
andaiProcess_FlipUVs
andaiProcess_FlipWindingOrder
flags. The output data matches Direct3D’s conventions: left-handed geometry, upper-left origin for UV coordinates and finally clockwise face order, suitable for CCW culling.
aiScene
先看一个简化版的数据结构:
Node
首先整个场景是以 node 的形式层级组织起来的,意义在于对某一节点进行任意形式的变换时,我们会希望对该节点的所有子节点应用相同的变换。比如一个挖掘机的大臂(动臂)挥动时连接着的小臂(斗杆)会自然地发生相同的变换。
那么 node 中存储的便是:
- 子 node 的索引
- 相对于父 node 的变换矩阵
- 对应 mesh 的索引
当然 Assimp 也提供了后处理 aiProcess_PreTransformVertices
来移除层级结构并将所有变换矩阵直接应用的到顶点坐标上,不过这个选项同时会移除动画数据。
按照 node 递归读取 scene 的伪代码大致是:
1 |
|
Mesh
可以发现 mesh 并不是存储于 node 中的,node 只持有一个对于 mesh 的引用,而所有 mesh 都平铺在 aiScene
中。同理 mesh 也持有一个 material 的引用,要拿着该索引回到 aiScene
才能拿到对应的材质。
这样一来 node 可以复用 mesh,mesh 可以复用 material。有一点绕,但就像 index 复用 vertex 一样合理。
不同在于一个 node 可以持有多个 mesh,而一个 mesh 只能持有一个 material。
mesh 结构体的定义看:mesh.h
Material
material 除了 GetTexture
接口可以拿到指定 aiTextureType
的贴图路径以外,还有 Get
接口可以拿到 Assimp 解析出来的材质定义。由于材质相关的定义与规范实属百花齐放,Assimp 也只提供了最简单的 MaterialProperty 的数据。
注意 Get
接口遵循十分严格的调用规范,并且需要由调用者保证正确的使用方式:Material-System。虽然约定俗成了不同 property 对应的数据类型,但好处是可以用简单的键值对获得十分复杂混乱的材质定义,也易于扩展,毕竟新增一个 MaterialProperty 也就是新增一对键值对的事。
样例
LearnOpenGL 是一个相对完整的例子。