本文共 3407 字,大约阅读时间需要 11 分钟。
首先介绍一下Assimp
库,它是Opengl中常使用的模型加载库,全称 Open Asset Import Library。它支持多种格式的模型文件,如obj、3ds、c4e等。模型一般通过、 或者这样的工具软件制作,然后可以导出模型文件。我们在使用Opengl时,就需要将这些文件中的数据内容解析出来,内容主要有顶点数据、法线、纹理坐标等,还有材质、光照等信息,只有解析出这些数据之后我们才能做后续的渲染工作。
当导入一个模型文件时,Assimp加载所有模型和场景数据到一个Scene类型的对象中,同时为场景节点、模型节点生成具有对应关系的数据结构。数据结构图如下:
从图片中看出结构结构对于初学者可能有些复杂,不过不要紧,只要先学会使用 Assimp库来把模型数据加载进来就可以了,后续可以自己尝试读取Obj等文件数据,对文件数据结构和解析过程进行细致的学习。
一般一个Model通常有多个网格构成,如一个人体模型可以由四肢、头部、上身等部位组成,每个部位都是一个Mesh。
那么怎么理解Mesh呢?如果你练习过绘制一个三角形的话,就知道一个三角形就是一个Mesh,包含顶点位置、法线、纹理坐标,其实这个三角形也是一个Face,而实际复杂的模型中的Mesh,一般包含很多Face,Face是绘制的基本图元,有三角形、点、线、多边形等,常用的主要是三角形。
<>
一个Mesh是顶点、边和面的结合,也是绘制3D物体的最小单元,一个3D模型,拥有越多的Mesh,这个模型越接近真实,但同时带来了硬件设备渲染的负担,特别是对于移动设备,如在手机上使用Opengl ES渲染时就要衡量GPU性能以及Mesh数目 的设定。(来自:)
好了,对Mesh有了一个大致的概念之后,我们就开始对模型进行加载,模型可以从网站上下载,本例中选择的模型也是Learn Opengl网上推荐的纳米铠甲模型,下载位置在。
一个Mesh应该至少需要一组顶点,每个顶点包含一个位置向量,一个法线向量,一个纹理坐标向量。一个网格也应该包含纹理(diffuse/specular map)的类型和ID。
struct Vertex {// Positionglm::vec3 Position;// Normalglm::vec3 Normal;// TexCoordsglm::vec2 TexCoords;};struct Texture { GLuint id; string type; aiString path;};
定义顶点和纹理的数据结构,索引的类型为GLuint类型
class Mesh {public: /* Mesh Data */ vectorvertices; vector indices; vector textures; // Constructor Mesh(vector vertices, vector indices, vector textures) { this->vertices = vertices; this->indices = indices; this->textures = textures; this->setupMesh(); } // Render the mesh void Draw(Shader shader) { ... //绑定相应的纹理,即设定片断着色器中的uniform类型 // 绘制网格 glBindVertexArray(this->VAO); glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); // 解绑定纹理 for (GLuint i = 0; i < this->textures.size(); i++) { glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GL_TEXTURE_2D, 0); } }private: GLuint VAO, VBO, EBO; void setupMesh() { ... //创建缓冲区、绑定、并分配顶点位置、法线、纹理数据 }};
完整的代码可以在找到。
要想使用Assimp库,需要在工程中配置相应的库文件,即导入dll文件和Include文件夹,Cmake的代码在下载。
class Model { public: /* 成员函数 */ Model(GLchar* path) { this->loadModel(path); } void Draw(Shader shader); private: /* 模型数据 */ vectormeshes; string directory; /* 私有成员函数 */ void loadModel(string path); void processNode(aiNode* node, const aiScene* scene); Mesh processMesh(aiMesh* mesh, const aiScene* scene); vector loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName);};
以上就是Model类,其中构造函数中传入的是路径,通过loadModel()函数加载obj类型的文件,然后得到 aiScene类型的对象,进而解析出meshes
Assimp::Importer importer;const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
Mesh类中需要vertices, indices, textures三种结构的vector,解析的过程也就是得到这三种数据并不断构造出Mesh类,然后pushback到meshes中,然后Model类的draw过程实际上就是遍历meshes中所有的Mesh实例,通过Mesh实来调用Mesh中draw()方法实现渲染。Model类的完整源代码在可以找到。
效果如下,该例中加入了光照,由于光源位置的关系和材质并没有都进行光照设定,效果并不是很好,可以自行设置光照,并影响到全部的材质上,应该会有不错的效果。