OpenGL(二)加载模型
来源:互联网 发布:英文app翻译软件 编辑:程序博客网 时间:2024/05/23 19:54
在OpenGL(一) OpenGL管线 与 可编程管线流程中,提到加载VBO、IBO的相关技术,本篇详细说一下。实际应用时,我们是不可能手写顶点和索引点。通常模型是使用3dMax或Maya制作,然后在OpenGL程序中 加载模型 。本文着重分析这些文件的格式以及 加载模型 的流程和方法。
大体流程
加载模型 的主要流程是:
- 读取模型文件内容
- 解析 vbo(vertex buffer object) 和 ibo(index buffer object) 信息。其中vbo包括顶点的位置、纹理坐标、法线。多个vbo可以打包成一个vao,具体vao的事情后面会有文章单独讨论,这里可以将其理解为是同样的概念。ibo主要是面对应的顶点围成面序列数组、顶点数,索引数。
- 将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);}
总结
通过以上这些步骤,就可以将一个模型文件加载并绘制出来。建议将其封装起来,因为这部分逻辑很常用,而且没有经常改动的必要。
关注我的微信公众号,获取更多优质内容
- OpenGL(二)加载模型
- OpenGl 加载渲染模型
- opengl模型加载
- opengl加载obj模型
- OpenGL---加载obj模型
- OpenGL -- OBJ 模型加载
- OpenGL 加载模型
- OpenGL进阶(二)自定义矩阵加载
- tensorflow-模型保存和加载(二)
- OpenGL学习:模型加载-obj模型和AssImp模型
- OpenGL(二十二) gluBuild2DMipmaps 加载Mip纹理贴图
- OpenGL学习脚印-AssImp模型加载
- OpenGL ES 加载3D模型
- OpenGL Shader 加载3DMax模型
- iOS OpenGL 加载3D模型
- OpenGL进阶(二) - 自定义矩阵加载
- Deeplearning4j例程(二) 加载本地模型预测未知图像
- 【JVM】类加载器与双亲委派模型(二)
- Android mipmap和drawable的区别
- [LeetCode]384. Shuffle an Array
- Common Subsequence POJ
- 函数式编程含义
- 构造方法也叫做构造函数OR构造器
- OpenGL(二)加载模型
- Zookeeper基本原理
- Android LayoutInflater原理解析
- ubuntu终端的快捷键
- 硬件改动导致软件配置错误的问题(更新ing)
- apk程序运行过程图(学习笔记)
- 【Unity3d游戏开发】浅谈Unity中的GC以及优化
- Spring+EhCache缓存实例
- ConvertUtils.register(new DateConverter(null), java.util.Date.class)使用