`本文主要介绍opengles 如何使用 第三方库Assimp(基于C++) 加载一个最简当的3d 立方体model
软硬件环境:
硬件:PC
软件:ubuntu22.04 egl1.4 weston9.0 opengles3.0 libassimp.so.5.2.0
模型通常都是通过Blender、3DS Max或者Maya这样的3D建模工具(3D Modeling Tool)精心制作出来的
模型的文件格式有很多种,每一种都会以它们自己的方式来导出模型数据。像是Wavefront的.obj这样的模型格式,只包含了模型数据以及材质信息,像是模型颜色和漫反射/镜面光贴图。而以XML为基础的Collada文件格式则非常的丰富,包含模型、光照、多种材质、动画数据、摄像机、完整的场景信息等等。Wavefront的.obj格式通常被认为是一个易于解析的模型格式。
最简单的3d 立方体model 文件 cube.obj 内容如下
cube.obj
# Number of vertices: 8
v -0.5 0.5 0.5
v -0.5 -0.5 0.5
v 0.5 -0.5 0.5
v 0.5 0.5 0.5
v 0.5 0.5 -0.5
v 0.5 -0.5 -0.5
v -0.5 -0.5 -0.5
v -0.5 0.5 -0.5
# Number of Polygons: 12
f 1 2 3
f 1 3 4
f 4 3 6
f 4 6 5
f 5 6 7
f 5 7 8
f 8 7 2
f 8 2 1
f 2 7 6
f 2 6 3
f 8 1 4
f 8 4 5
在PC 上使用3D 查看器打开cube.obj 文件,如下图所示

一个非常流行的模型导入库是Assimp,它是Open Asset Import Library(开放的资产导入库)的缩写。Assimp能够导入很多种不同的模型文件格式(并也能够导出部分的格式),它会将所有的模型数据加载至Assimp的通用数据结构中。当Assimp加载完模型之后,我们就能够从Assimp的数据结构中提取我们所需的所有数据了。由于Assimp的数据结构保持不变,不论导入的是什么种类的文件格式,它都能够将我们从这些不同的文件格式中抽象出来,用同一种方式访问我们需要的数据。
当使用Assimp导入一个模型的时候,它通常会将整个模型加载进一个场景(Scene)对象,它会包含导入的模型/场景中的所有数据。Assimp会将场景载入为一系列的节点(Node),每个节点包含了场景对象中所储存数据的索引,每个节点都可以有任意数量的子节点。Assimp数据结构的(简化)模型如下:

所以,我们需要做的第一件事是将一个物体加载到Scene对象中,遍历节点,获取对应的Mesh对象(我们需要递归搜索每个节点的子节点),并处理每个Mesh对象来获取顶点数据、索引以及它的材质属性。最终的结果是一系列的网格数据,我们会将它们包含在一个Model对象中
使用如下命令可以在ubuntu 上安装 libassimp
sudo apt install libassimp-dev
安装完成后,在/usr/lib/x86_64-linux-gnu/目录下就能看到libassimp相关的库文件,如下图所示

#include
#include
#include
// 使用 Assimp 加载模型文件
#include
#include
#include
int main()
{
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile("./cube.obj", aiProcess_Triangulate | aiProcess_FlipUVs);
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
// 加载模型失败
printf("cube.obj load failed\n");
return -1;
}
aiMesh* mesh = scene->mMeshes[0];
printf("mesh->mNumVertices = %d\n", mesh->mNumVertices);
printf("mesh->mNumFaces = %d\n", mesh->mNumFaces);
// 获取顶点坐标数据
printf("cube vVertices : \n");
GLfloat* vVertices = new GLfloat[mesh->mNumVertices * 3];
for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
vVertices[i * 3] = mesh->mVertices[i].x;
vVertices[i * 3 + 1] = mesh->mVertices[i].y;
vVertices[i * 3 + 2] = mesh->mVertices[i].z;
printf("%.1f, %.1f, %.1f,\n", vVertices[i * 3], vVertices[i * 3 + 1], vVertices[i * 3 + 2]);
}
// 获取面索引数据
printf("cube indices : \n");
GLushort* indices = new GLushort[mesh->mNumFaces * 3];
for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
for (unsigned int j = 0; j < 3; j++) {
indices[i * 3 + j] = mesh->mFaces[i].mIndices[j];
}
printf("%d, %d, %d,\n", indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]);
}
return 0;
}
使用如下命令对 assimp_load_cube.cpp 文件进行编译
g++ assimp_load_cube.cpp -o assimp_load_cube -lassimp
编译成功会在当前目录下得到可执行文件 assimp_load_cube
将cube.obj 和 可执行文件 assimp_load_cube 放到同一个目录下,执行 ./assimp_load_cube 命令,打印解析出来的顶点坐标和顶点索引信息如下:
lkh@ubuntu:~/lkh2024/opengles_test$ ./assimp_load_cube
mesh->mNumVertices = 36
mesh->mNumFaces = 12
cube vVertices :
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5