OpenGL(二)加载模型

来源:互联网 发布:英文app翻译软件 编辑:程序博客网 时间:2024/05/23 19:54

在OpenGL(一) OpenGL管线 与 可编程管线流程中,提到加载VBO、IBO的相关技术,本篇详细说一下。实际应用时,我们是不可能手写顶点和索引点。通常模型是使用3dMax或Maya制作,然后在OpenGL程序中 加载模型 。本文着重分析这些文件的格式以及 加载模型 的流程和方法。

大体流程

加载模型 的主要流程是:

  1. 读取模型文件内容
  2. 解析 vbo(vertex buffer object) 和 ibo(index buffer object) 信息。其中vbo包括顶点的位置、纹理坐标、法线。多个vbo可以打包成一个vao,具体vao的事情后面会有文章单独讨论,这里可以将其理解为是同样的概念。ibo主要是面对应的顶点围成面序列数组、顶点数,索引数。
  3. 将vbo和ibo传给GPU,根据shader绘制对应图形。

模型存储结构

在了解如何 加载模型 之前,先要明确模型文件的存储方式。在众多的格式中以obj格式比较通用,它内部是以文本形式表达的。以最简单的obj模型为例,一个Quad的信息可能是这样:

# This file uses centimeters as units for non-parametric coordinates.mtllib Quad.mtlg defaultv -0.500000 -0.500000 0.000000v 0.500000 -0.500000 0.000000v -0.500000 0.500000 0.000000v 0.500000 0.500000 0.000000vt 0.000000 0.000000vt 1.000000 0.000000vt 0.000000 1.000000vt 1.000000 1.000000vn 0.000000 0.000000 1.000000vn 0.000000 0.000000 1.000000vn 0.000000 0.000000 1.000000vn 0.000000 0.000000 1.000000s 1g Quadusemtl initialShadingGroupf 1/1/1 2/2/2 3/3/3f 3/3/3 2/2/2 4/4/4

简单解释一下文件的含义:

  • 以#开始的行为注释行
  • usemtl和mtllib表示的材质相关数据。
  • o 引入一个新的object
  • v 表示顶点位置
  • vt 表示顶点纹理坐标
  • vn 表示顶点法向量
  • f 表示一个面,面使用1/2/8这样格式,表示顶点位置/纹理坐标/法向量的索引,这里索引的是前面用v,vt,vn定义的数据 注意这里Obj的索引是从1开始的,而不是0。

通过二进制读取文件的方式,我们可以很容易的读取到顶点信息,并将其保存到内存结构中。这里有个要注意的地方,就是顶点是动态组建出来的。例如这种面:

f 5/5/9 6/6/10 7/7/11

是可能由少数顶点信息组建出的面。因此顶点索引需要动态生成,而不仅仅是看顶点信息的个数。换句话说,顶点索引是由面信息决定的。因此,为了保证ibo与vbo的对应性,在组建vbo数组时,应该从组成的面入手,按照顺序动态创建顶点,或记录已存在顶点的索引,生成对应的ibo,以保证最大限度的复用顶点。与此同时,还应该记录vbo和ibo的数量,其中vbo的数量用于计算生成的内存大小,ibo数量用于绘制时计算面的构成。

数据解析

绘制一个Mesh的时候,最好构建一个类结构,一个顶点的结构体可能是这样:

struct Vertex{    glm::vec3 Position;    glm::vec3 Normal;    glm::vec2 TexCoords;};class Mesh{Public:    vector<Vertex> vertices;    vector<GLuint> indices;    vector<Texture> textures;    Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> texture);    Void Draw(Shader shader);private:    GLuint VAO, VBO, IBO;    void setupMesh();public :    Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures)    {        this->vertices = vertices;        this->indices = indices;        this->textures = textures;        this->setupMesh();    }private:    void setupMesh()    {        glGenVertexArrays(1, &this->VAO);        glGenBuffers(1, &this->VBO);        glGenBuffers(1, &this->IBO);        glBindVertexArray(this->VAO);        glBindBuffer(GL_ARRAY_BUFFER, this->VBO);        glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->IBO);        glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);        // 设置顶点坐标指针        glEnableVertexAttribArray(0);         glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);        // 设置法线指针        glEnableVertexAttribArray(1);         glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),(GLvoid*)offsetof(Vertex, Normal));        // 设置顶点的纹理坐标        glEnableVertexAttribArray(2);         glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),(GLvoid*)offsetof(Vertex, TexCoords));        glBindVertexArray(0);    }}

辅助库实现

现在市面上有一个很流行的模型加载库,叫做Assimp(Open Asset Import Library)。Assimp可以导入几十种不同格式的模型文件(同样也可以导出部分模型格式)。你可以在官网下载。我们可以对其做一些封装,来辅助我们 加载模型

#include <assimp/Importer.hpp>#include <assimp/scene.h>#include <assimp/postprocess.h>void loadModel(string path){    Assimp::Importer import;    const aiScene* scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);     if(!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)     {        cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl;        return;    }    this->processNode(scene->mRootNode, scene);}void processNode(aiNode* node, const aiScene* scene){    // 添加当前节点中的所有Mesh    for(GLuint i = 0; i < node->mNumMeshes; i++)    {        aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];         this->processMesh(mesh, scene);     }     // 递归处理该节点的子孙节点    for(GLuint i = 0; i < node->mNumChildren; i++)    {        this->processNode(node->mChildren[i], scene);    }}Mesh processMesh(aiMesh* mesh, const aiScene* scene){    vector<Vertex> vertices;    vector<GLuint> indices;    vector<Texture> textures;    for(GLuint i = 0; i < mesh->mNumVertices; i++)    {       glm::vec3 vector;        vector.x = mesh->mVertices[i].x;       vector.y = mesh->mVertices[i].y;       vector.z = mesh->mVertices[i].z;        vertex.Position = vector;       vertices.push_back(vertex);    }    for(GLuint i = 0; i < mesh->mNumFaces; i++)    {        aiFace face = mesh->mFaces[i];        for(GLuint j = 0; j < face.mNumIndices; j++)            indices.push_back(face.mIndices[j]);    }      if(mesh->mTextureCoords[0])    {        glm::vec2 vec;        vec.x = mesh->mTextureCoords[0][i].x;         vec.y = mesh->mTextureCoords[0][i].y;        vertex.TexCoords = vec;    }    else        vertex.TexCoords = glm::vec2(0.0f, 0.0f);    return Mesh(vertices, indices, textures);}

通过这样的实现可以构建 加载模型 到OpenGL中。更详细的实现可以参考这里。

GPU绘制

这部分与绘制一个普通的三角形就没有区别了,从内存中读取vbo或vao,并且读取ibo,上传到GPU中,根据上一步获得的ibo数量进行绘制。代码可以简化为:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,this->VAO);glDrawElements(GL_TRIANGLES,this->indices,GL_UNSIGNED_INT,0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);

其对应的shader为:

attribute vec3 pos;attribute vec2 texcoord;attribute vec3 normal;uniform mat4 M;uniform mat4 V;uniform mat4 P;varying vec4 V_Color;void main(){    gl_Position=P*V*M*vec4(pos,1.0);}

总结

通过以上这些步骤,就可以将一个模型文件加载并绘制出来。建议将其封装起来,因为这部分逻辑很常用,而且没有经常改动的必要。


松阳论道

关注我的微信公众号,获取更多优质内容

0 0
原创粉丝点击