首页 > 其他分享 >OpenGL 模型加载详解

OpenGL 模型加载详解

时间:2023-11-11 11:44:46浏览次数:41  
标签:OpenGL 模型 绑定 纹理 Assimp Mesh GL 详解 加载

1. Assimp

目前为止,我们已经可以绘制一个物体,并添加不同的光照效果了。但是我们的顶点数据太过简单,只能绘制简单的立方体。但是房子汽车这种不规则的形状我们的顶点数据就很难定制了。索性,这部分并不需要我们苦逼的开发人员去考虑。成熟的3D建模工具可以将设计师设计的模型导出模型文件,借助模型加载库就可以将他们转化为顶点数据。

 2. 模型加载库

一个非常流行的模型导入库是Assimp,它是Open Asset Import Library(开放的资产导入库)的缩写。Assimp能够导入很多种不同的模型文件格式(并也能够导出部分的格式),它会将所有的模型数据加载至Assimp的通用数据结构中。当Assimp加载完模型之后,我们就能够从Assimp的数据结构中提取我们所需的所有数据了。由于Assimp的数据结构保持不变,不论导入的是什么种类的文件格式,它都能够将我们从这些不同的文件格式中抽象出来,用同一种方式访问我们需要的数据。

当使用Assimp导入一个模型的时候,它通常会将整个模型加载进一个场景(Scene)对象,它会包含导入的模型/场景中的所有数据。Assimp会将场景载入为一系列的节点(Node),每个节点包含了场景对象中所储存数据的索引,每个节点都可以有任意数量的子节点。Assimp数据结构的(简化)模型如下:

 这个结构图是一个模型在assimp中的基础结构,如果看不懂也没关系,到后面我们会频繁的使用它。

3. 网格

在上一节中,我们知道了Assimp中的基本单元式Mesh或者Model。这一节中我们就先定义一个自己的Mesh类。

4. Mesh

 

Mesh应该作为一个最基本的绘制单元,那么他应该自己维护VAO、VBO、EBO这些数据。并且他应该具备自动绑定数据及定义针对自身的Mesh自动设置数据格式等功能。

 

之前我们都是一个float数组来表示,这是因为GL在绑定VAO的时候需要的是一个连续的内存,我们通过指定数据其实地址和数据长度就可以告诉GL如何去绑定数据。但是数组看起来并不是一个直观的形式,我们希望能找到一个更加明了的形式来方便我们查看数据。庆幸的是,结构体中的内存地址是连续的。我们将数组中的数值替换成结构体,这样我们可以清楚地区分出不同顶点。

 

此外,数组我们也可以进一步简化一下:数组的长度是一个定长,需要在一开始就指定数组长度,而且数组的元素个数也需要计算才可以获得。c++中提供了一个很好的扩展就是向量vector。

 

这里我们直接放一下Mesh类的代码,注意现在我们还没有将他写成一个通用的Mesh类,而是针对当前箱子模型的片段着色器写的一个Mesh类。

  1 #ifndef Mesh_h
  2 #define Mesh_h
  3 
  4 ///system framework
  5 #include <vector>
  6 
  7 ///third party framework
  8 #include <glm/glm.hpp>
  9 #include <glm/gtc/matrix_transform.hpp>
 10 #include <glm/gtc/type_ptr.hpp>
 11 
 12 ///custom framework
 13 #include "Shader.h"
 14 
 15 using namespace std;
 16 
 17 struct Mesh_Vertex {
 18     glm::vec3 Position;
 19     glm::vec3 Normal;
 20     glm::vec2 TexCoords;
 21 };
 22 
 23 ///纹理结构体(标明已经加载的纹理的纹理ID及纹理对应类型)
 24 struct Mesh_Texture {
 25     unsigned int t_id;
 26     string type;
 27 };
 28 
 29 class Mesh {
 30 public:
 31     
 32     vector<Mesh_Vertex> vertices;
 33     vector<unsigned int> indices;
 34     vector<Mesh_Texture> textures;
 35     unsigned int VAO;
 36     
 37     Mesh(vector<Mesh_Vertex> aVertices, vector<unsigned int> aIndices, vector<Mesh_Texture> aTextures) {
 38         vertices = aVertices;
 39         indices = aIndices;
 40         textures = aTextures;
 41         setupMesh();
 42     }
 43     
 44     Mesh(vector<Mesh_Vertex> aVertices, vector<unsigned int> aIndices) {
 45         vertices = aVertices;
 46         indices = aIndices;
 47         setupMesh();
 48     }
 49     
 50     Mesh() {
 51         
 52     }
 53     
 54     void Draw(Shader shader) {
 55         for (int i = 0; i < textures.size(); ++i) {
 56             ///首先激活指定位置的纹理单元
 57             glActiveTexture(GL_TEXTURE0 + i);
 58             string name;
 59             string type = textures[i].type;
 60             if (type == "diffuse") {
 61                 name = "material.diffuse";
 62             } else if (type == "specular") {
 63                 name = "material.specular";
 64             }
 65             shader.setInt(name, i);
 66             glBindTexture(GL_TEXTURE_2D,textures[i].t_id);
 67         }
 68         DrawWithoutConfigImage();
 69         ///结束顶点数组对象的绑定
 70         glBindVertexArray(0);
 71         glActiveTexture(GL_TEXTURE0);
 72     }
 73     
 74     void DrawWithoutConfigImage() {
 75         glBindVertexArray(VAO);
 76         glDrawElements(GL_TRIANGLES, (int)indices.size(), GL_UNSIGNED_INT, 0);
 77     }
 78     
 79     void ReleaseMesh() {
 80         ///释放对象
 81         glDeleteVertexArrays(1, &VAO);
 82         glDeleteBuffers(1, &VBO);
 83         glDeleteBuffers(1, &EBO);
 84     }
 85     
 86 private:
 87     unsigned int VBO,EBO;
 88     
 89     void setupMesh(){
 90         glGenVertexArrays(1,&VAO);
 91         glGenBuffers(1,&VBO);
 92         glGenBuffers(1,&EBO);
 93         glBindVertexArray(VAO);
 94         glBindBuffer(GL_ARRAY_BUFFER,VBO);
 95         glBufferData(GL_ARRAY_BUFFER,vertices.size() * sizeof(Mesh_Vertex),&vertices[0],GL_STATIC_DRAW);
 96         glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Mesh_Vertex),(void *)(offsetof(Mesh_Vertex, Position)));
 97         glEnableVertexAttribArray(0);
 98         glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(Mesh_Vertex),(void *)(offsetof(Mesh_Vertex, Normal)));
 99         glEnableVertexAttribArray(1);
100         glVertexAttribPointer(2,2,GL_FLOAT,GL_FALSE,sizeof(Mesh_Vertex),(void *)(offsetof(Mesh_Vertex, TexCoords)));
101         glEnableVertexAttribArray(2);
102         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
103         glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
104         glBindVertexArray(0);
105         glBindBuffer(GL_ARRAY_BUFFER, 0);
106         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
107     }
108 };
109 
110 #endif

我们看到我们只是将原来在main.mm中的数据绑定过程移到了Mesh类中,其他的地方基本没有什么变化。

5. 通用Mesh类

观察我们上面的代码,我们唯一不通用的地方就是纹理绑定的时候。如果想通用,就要求我们的片段着色器中的纹理命名应该是可以用一个通式表达出来的形式。如:

 

 那么如果是这样的,我们的绑定部分就可以改造成这个样子:

这里还是简单的解释一下我们的Mesh类工作的流程。

  • 1.初始化时传入顶点数据、索引数据、纹理数据(这里我们确定了绘制什么、如何绘制的问题)。
  • 2.自动绑定VAO、EBO。获取到可复用的模型对象。
  • 3.绘制时每次都重新绑定GL当前激活的纹理单元,并按照索引绘制模型。

几个可以重点解释的地方:

  • 1.传入Mesh类的实际为已经提交给GL的纹理的ID。在外界的时候我们加载图像后,GL中即已存在该纹理的一份拷贝,我们可以通过GL返回给我们的ID找到对应的数据。在想要使用的时候只要将指定位置的纹理单元激活后将对应的ID绑定在该纹理单元上即可让激活的纹理单元上的数据指向指定纹理数据,而后再将片段着色器中纹理绑定为指定纹理单元即可。
  • 2.GL中可用的纹理单元是有限的,故而我们要反复使用纹理单元,所以在每次使用前应重新绑定纹理纹理数据。当然这是相对的,如果你使用的纹理单元足够少而不用复用的话,你也可以只绑定一次。具体还是要视情况而定。
  • 3.在每一次Mesh绘制完毕后,我们要记得恢复当前激活的纹理位置为GL_TEXTURE0。这样是为了保持其与系统默认行为一致,不至于引起额外变量。

 

标签:OpenGL,模型,绑定,纹理,Assimp,Mesh,GL,详解,加载
From: https://www.cnblogs.com/rmb999/p/17825713.html

相关文章

  • 十二、类加载器和反射
    一、类的加载类的加载当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。加载:就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。连接:验证是否有正确的内部结构,并和......
  • 基于 three.js 加载器分别加载模型
    点击查看代码/***参数:模型文件路径,成功回调函数**基于three.js加载器分别加载模型**全部加载后通过回调函数传出打印*/import{FBXLoader}from'three/examples/jsm/loaders/FBXLoader.js'import{GLTFLoader}from'three/examples/jsm/loaders/GLTF......
  • 简单全面的mybatis详解
    1、前言MyBatis是一款ORM(Object-RelationalMapping)框架,其主要用于将Java对象与关系数据库之间进行映射,凭借其轻量性、稳定性以及广泛的开源社区其受到了广大开发者的追捧。那MyBatis为我们做了哪些事情呢?其实,总结来看主要有如下几点:SQL映射配置:MyBatis使用XML或注解配置文件来定义......
  • Maven详解
    一.前言   以前做过的项目中,没有真正的使用过Maven,只知道其名声很大,其作用是用来管理jar包的。最近一段时间在项目过程中使用Maven,用Maven构建的web项目,其项目结构只停留在了解阶段,没有深入的使用与理解,刚好最近看了一篇关于Maven的详解;就开始深入学习一下Maven的具体应用。......
  • Vue中网络图片懒加载工具
    在滑动列表视图中如果有网络图片需要加载直接给imag标签赋值src,会造成没有显示的item中图片也直接加载,势必浪费网络资源。创建一个插件,让列表中的item出显的时候在加载图片从而减少网络请求。具体方法就是给img标签添加一个新的属性暂时先保存对应的url,当item滑动出现到一定值时......
  • JAVA分布式详解
    分布式系统是由多个独立的计算机(或计算节点)通过网络连接组成的系统,这些计算机共同工作以完成某个任务。在Java中,有许多工具和框架可以帮助开发者构建分布式系统。以下是一些与Java分布式系统相关的重要概念、技术和框架:JavaRMI(RemoteMethodInvocation):JavaRMI是一种用于创建分......
  • 修改Web网页中资源加载的优先级
    在Chrome浏览器中,网络请求的优先级分成了5个等级:Highest最高,如页面HTML资源和CSS文件;High高,如正文图片请求资源;Medium中等,如页面的业务JavaScript文件请求;Low低,如内联的Base64资源,异步加载的JavaScript文件请求;Lowest最低,如发送的统计请求;调整preload预加载默认的优......
  • C++字符串详解
    C++大大增强了对字符串的支持,除了可以使用C风格的字符串,还可以使用内置的string类。string类处理起字符串来会方便很多,完全可以代替C语言中的字符数组或字符串指针。string是C++中常用的一个类,它非常重要,我们有必要在此单独讲解一下。使用string类需要包含头文件<string>,......
  • (四)Spring源码解析:bean的加载流程解析
    一、概述在前几讲中,我们着重的分析了Spring对xml配置文件的解析和注册过程。那么,本节内容,将会试图分析一下bean的加载过程。具体代码,如下图所示:1.1>doGetBean(...)针对bean的创建和加载,我们可以看出来逻辑都是在doGetBean(...)这个方法中的,所以,如下就是针对于这个方法的整体源码注......
  • helm 详解
    helm定位为包管理工具   ......