赞
踩
背景
到现在为止我们都在使用手动生成的模型。正如你所想的,指明每个顶点的位置和其他属性有点时候并不是十分方便。对于一个箱子、锥体和简单平面还好,但是像人们的脸怎么办?现实的商业应用和游戏中,程序中使用模型一般都是由美术人员通过如 Blender, Maya 或 3ds Max 等建模软件来解决这个问题。这些软件提供了高级的工具帮助他们创造很复杂的模型。模型完成后可以以不同格式的文件保存。文件中包含了这个模型的所有几何解释。这些文件可以被加载到一个游戏引擎(提供支持特定格式的引擎)里面,文件中的内容可用来填充到渲染所需要的顶点缓存和索引缓存中。使用这些专业的模型对于场景效果的提升是十分关键的。
这课没有太多的理论。让我们直接去看如何使用 Assimp 库中提供的函数来导入 3D 模型。在开始之前,请先确认你已经从上面的链接安装了 Assimp。
- (mesh.h:50)
- class Mesh
- {
- public:
- Mesh();
- ~Mesh();
- bool LoadMesh(const std::string& Filename);
- void Render();
- private:
- bool InitFromScene(const aiScene* pScene, const std::string& Filename);
- void InitMesh(unsigned int Index, const aiMesh* paiMesh);
- bool InitMaterials(const aiScene* pScene, const std::string& Filename);
- void Clear();
- #define INVALID_MATERIAL 0xFFFFFFFF
- struct MeshEntry {
- MeshEntry();
- ~MeshEntry();
- bool Init(const std::vector& Vertices,
- const std::vector& Indices);
- GLuint VB;
- GLuint IB;
- unsigned int NumIndices;
- unsigned int MaterialIndex;
- };
- std::vector<MeshEntry> m_Entries;
- std::vector<Texture*> m_Textures;
- };

- (mesh.cpp:77)
- bool Mesh::LoadMesh(const std::string& Filename)
- {
- // Release the previously loaded mesh (if it exists)
- Clear();
- bool Ret = false;
- Assimp::Importer Importer;
- const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);
- if (pScene) {
- Ret = InitFromScene(pScene, Filename);
- }
- else {
- printf("Error parsing '%s': '%s'\n", Filename.c_str(), Importer.GetErrorString());
- }
- return Ret;
- }

- (mesh.cpp:97)
- bool Mesh::InitFromScene(const aiScene* pScene, const std::string& Filename)
- {
- m_Entries.resize(pScene->mNumMeshes);
- m_Textures.resize(pScene->mNumMaterials);
- // Initialize the meshes in the scene one by one
- for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
- const aiMesh* paiMesh = pScene->mMeshes[i];
- InitMesh(i, paiMesh);
- }
- return InitMaterials(pScene, Filename);
- }
首先我们根据需要用到的 m_Entries 和 m_Textures 数量来为其分配存储空间,其数目可经由
aiScene 对象中的成员 mNumMeshes 和 mNumMaterials 得到。接下来我们遍历
aiScene 对象中的 mMeshes 数组,并挨个儿初始化 m_Entries 实例。最后初始化材质。
- (mesh.cpp:111)
- void Mesh::InitMesh(unsigned int Index, const aiMesh* paiMesh)
- {
- m_Entries[Index].MaterialIndex = paiMesh->mMaterialIndex;
- std::vector Vertices;
- std::vector Indices;
- ...
- (mesh.cpp:118)
- const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
- for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) {
- const aiVector3D* pPos = &(paiMesh->mVertices[i]);
- const aiVector3D* pNormal = &(paiMesh->mNormals[i]) : &Zero3D;
- const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;
- Vertex v(Vector3f(pPos->x, pPos->y, pPos->z),
- Vector2f(pTexCoord->x, pTexCoord->y),
- Vector3f(pNormal->x, pNormal->y, pNormal->z));
- Vertices.push_back(v);
- }
- ...
这里我们通过对模型数据的解析将顶点属性数据依次存放到我们的 Vertices 容器中。我们使用到
aiMesh 类中下面的一些方法:
- (mesh.cpp:132)
- for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) {
- const aiFace& Face = paiMesh->mFaces[i];
- assert(Face.mNumIndices == 3);
- Indices.push_back(Face.mIndices[0]);
- Indices.push_back(Face.mIndices[1]);
- Indices.push_back(Face.mIndices[2]);
- }
- ...
- (mesh.cpp:140)
- m_Entries[Index].Init(Vertices, Indices);
- }
- (mesh.cpp:143)
- bool Mesh::InitMaterials(const aiScene* pScene, const std::string& Filename)
- {
- for (unsigned int i = 0 ; i < pScene->mNumMaterials ; i++) {
- const aiMaterial* pMaterial = pScene->mMaterials[i];
- ...
这个函数加载模型所用的所有纹理。在
aiScene 对象中 mNumMaterials 属性存放材质数量,而 mMaterials 是一个指针数组,其中的每一个元素都指向一个
aiMaterials 结构体。
aiMaterials 结构体十分复杂,但是它通过几个 API 对其进行了封装。
- (mesh.cpp:165)
- m_Textures[i] = NULL;
- if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
- aiString Path;
- if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
- std::string FullPath = Dir + "/" + Path.data;
- m_Textures[i] = new Texture(GL_TEXTURE_2D, FullPath.c_str());
- if (!m_Textures[i]->Load()) {
- printf("Error loading texture '%s'\n", FullPath.c_str());
- delete m_Textures[i];
- m_Textures[i] = NULL;
- Ret = false;
- }
- }
- }
- ...

一个材质可以包含多个的纹理,但并不是所有的纹理都必须包含颜色。例如,一个纹理可以是高度图、法向图、位移图等。因为当前我们针对光照计算的着色器程序只使用一个纹理,所以我们也只关心漫反射纹理。因此,我们使用
aiMaterial::GetTextureCount() 函数检查有多少漫反射纹理存在。这个函数以纹理类型为参数而返回此特定类型纹理的数目。如果至少存在一个漫反射纹理,我们使用
aiMaterial::GetTexture() 函数来获取它。这个函数的第一个参数是类型,接下来是纹理索引,然后我们需要一个指向纹理文件路径的字符串指针。最后有 5 个指针参数允许我们去获取纹理的各种配置,比如混合因子、全图模式和纹理操作等。这些是可选的,现在我们忽略它们而只传递 NULL。这里我们假定模型和纹理在同一子目录中。如果模型的结构比较复杂,你可能需要在别处寻找纹理,那样的话我们可以像往常一样创建纹理对象并加载它。
- (mesh.cpp:187)
- if (!m_Textures[i]) {
- m_Textures[i] = new Texture(GL_TEXTURE_2D, "./white.png");
- Ret = m_Textures[i]->Load();
- }
- }
- return Ret;
- }
上面这一小段代码用于处理你在模型加载时遇到的一些问题。有时候一个模型可能并没有纹理,这样的话你可能会看不到任何东西,因为如果纹理不存在的话取样的结果默认为黑色。在这里当我们遇到这种问题时我们为其加载一个白色的纹理(你将在附件中找到这个纹理)。这将使得所有像素的基色变为白色,这样可能看起来不是很好,但是至少你可以看到一些东西。
- (mesh.cpp:197)
- void Mesh::Render()
- {
- glEnableVertexAttribArray(0);
- glEnableVertexAttribArray(1);
- glEnableVertexAttribArray(2);
- for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
- glBindBuffer(GL_ARRAY_BUFFER, m_Entries[i].VB);
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
- glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
- glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Entries[i].IB);
- const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;
- if (MaterialIndex < m_Textures.size() && m_Textures[MaterialIndex]) {
- m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
- }
- glDrawElements(GL_TRIANGLES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);
- }
- glDisableVertexAttribArray(0);
- glDisableVertexAttribArray(1);
- glDisableVertexAttribArray(2);
- }

这个函数封装了 mesh 的渲染,并将其从主函数中分离出来(以前是主函数的一部分)。它遍历 m_Entries 数组,将数组中每个元素。节点的材质索引被用来从 m_Texture 数组中取出纹理对象,并将这个纹理绑定。最后,执行绘制命令。现在我们有多个已经从文件中加载进来的 mesh 对象,调用 Mesh::Render() 函数一个接一个渲染它们。
glEnable(GL_DEPTH_TEST);
- (glut_backend.cpp:73)
- glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
- (tutorial22.cpp:95)
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
操作结果:
预编译的Assimp 3.0 lib for Android(需要翻墙)
Compile Assimp Open Source Library For Android
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。