OpenGL加载模型 之网格

这篇具有很好参考价值的文章主要介绍了OpenGL加载模型 之网格。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基础知识点

  • 我们的工作就是去解析这些导出的模型文件,并将其中的模型数据存储为OpenGL能够使用的数据。一个常见的问题是,导出的模型文件通常有几十种格式,不同的工具会根据不同的文件协议把模型数据导出到不同格式的模型文件中。
  • 有的模型文件格式只包含模型的静态形状数据和颜色、漫反射贴图、高光贴图这些基本的材质信息,比如Wavefront的.obj文件。而有的模型文件则采用XML来记录数据,且包含了丰富的模型、光照、各种材质、动画、摄像机信息和完整的场景信息等,比如Collada文件格式。Wavefront的obj格式是为了考虑到通用性而设计的一种便于解析的模型格式。
  • Assimp,全称为Open Asset Import Library。Assimp可以导入几十种不同格式的模型文件(同样也可以导出部分模型格式)。只要Assimp加载完了模型文件,我们就可以从Assimp上获取所有我们需要的模型数据。Assimp把不同的模型文件都转换为一个统一的数据结构,所有无论我们导入何种格式的模型文件,都可以用同一个方式去访问我们需要的模型数据。
  • 所有的模型、场景数据都包含在scene对象中,如所有的材质和Mesh。同样,场景的根节点引用也包含在这个scene对象中
  • 一个Mesh还会包含一个Material(材质)对象用于指定物体的一些材质属性。如颜色、纹理贴图(漫反射贴图、高光贴图等)
  • 一个Mesh会包含多个面片。一个Face(面片)表示渲染中的一个最基本的形状单位,即图元(基本图元有点、线、三角面片、矩形面片)。一个面片记录了一个图元的顶点索引,通过这个索引,可以在mMeshes[]中寻找到对应的顶点位置数据。顶点数据和索引分开存放,可以便于我们使用缓存(VBO、NBO、TBO、IBO)来高速渲染物体。(详见Hello Triangle)
  • 一个Mesh还会包含一个Material(材质)对象用于指定物体的一些材质属性。如颜色、纹理贴图(漫反射贴图、高光贴图等)

 所以我们要做的第一件事,就是加载一个模型文件为scene对象,然后获取每个节点对应的Mesh对象(我们需要递归搜索每个节点的子节点来获取所有的节点),并处理每个Mesh对象对应的顶点数据、索引以及它的材质属性。最终我们得到一个只包含我们需要的数据的Mesh集合。
OpenGL加载模型 之网格

  • 网格:用建模工具构建物体时,美工通常不会直接使用单个形状来构建一个完整的模型。一般来说,一个模型会由几个子模型/形状组合拼接而成。而模型中的那些子模型/形状就是我们所说的一个网格。例如一个人形模型,美工通常会把头、四肢、衣服、武器这些组件都分别构建出来,然后在把所有的组件拼合在一起,形成最终的完整模型。一个网格(包含顶点、索引和材质属性)是我们在OpenGL中绘制物体的最小单位。一个模型通常有多个网格组成。

网格

  • 使用Assimp可以把多种不同格式的模型加载到程序中,但是一旦载入,它们就都被储存为Assimp自己的数据结构。我们最终的目的是把这些数据转变为OpenGL可读的数据,才能用OpenGL来渲染物体。
  • 一个网格(Mesh)代表一个可绘制实体,现在我们就定义一个自己的网格类。一个网格应该至少需要一组顶点,每个顶点包含一个位置向量,一个法线向量,一个纹理坐标向量。一个网格也应该包含一个索引绘制用的索引,以纹理(diffuse/specular map)形式表现的材质数据。
     话不多说,直接上代码,解释都在注释中:
#pragma once
// Std. Includes
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;
// GL Includes
#include <GL/glew.h> // Contains all the necessery OpenGL includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// 顶点结构体(包含位置、法向量、纹理坐标)
struct Vertex {
    // Position
    glm::vec3 Position;
    // Normal
    glm::vec3 Normal;
    // TexCoords
    glm::vec2 TexCoords;
};

// 纹理结构体(包含纹理id、纹理贴图类型、)
struct Texture {
    GLuint id;
    string type;
    aiString path;
};

// 网格类
class Mesh {
public:
    /*  网格存储的数据:一系列 顶点、渲染索引顶点、纹理  */
    vector<Vertex> vertices;
    vector<GLuint> indices;
    vector<Texture> textures;

    /*  网格的构造函数,传入网格所有数据 并调用网格设置函数  */
    Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures)
    {
        this->vertices = vertices;
        this->indices = indices;
        this->textures = textures;

        // 设置网格的缓冲区
        this->setupMesh();
    }

    // 依据传入的着色器程序渲染网格
    void Draw(Shader shader)
    {
        // 记录对应纹理类型的数目(因为在着色器中命名的采样器也是按照类型分类的)
        GLuint diffuseNr = 1;
        GLuint specularNr = 1;
        // 激活纹理单元、设置着色器中采样器的位置值、绑定纹理到对应纹理类型上(作用:将纹理赋值给着色器的采样器)
        for (GLuint i = 0; i < this->textures.size(); i++)
        {
            // 激活对应着色器上的对应纹理单元
            glActiveTexture(GL_TEXTURE0 + i); 
            
            // 使用stringstram方便从GL_Luint和string之间的转换
            stringstream ss;
            string number;
            // 获得纹理的类型
            string name = this->textures[i].type;
            // 根据纹理类型得到并记录 对应纹理类型的数目
            if (name == "texture_diffuse")
                ss << diffuseNr++;  // 属于漫反射贴图
            else if (name == "texture_specular")
                ss << specularNr++; // 属于高光贴图
            number = ss.str();

            // 设置着色器中采样器的位置值为i,其中(name+number).c_str() 返回 "texture_diffuseN"或"texture_specular"
            glUniform1i(glGetUniformLocation(shader.Program, (name + number).c_str()), i);
            // 绑定当前迭代的纹理 到对应纹理类型上,将纹理赋值给着色器中的采样器
            glBindTexture(GL_TEXTURE_2D, this->textures[i].id);
        }

        // 设置着色器中材质的高光发光值
        glUniform1f(glGetUniformLocation(shader.Program, "material.shininess"), 16.0f);

        // 绑定网格的VAO
        glBindVertexArray(this->VAO);
        // 根据网格的索引数据进行网格的渲染
        glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);
        // 解绑VAO
        glBindVertexArray(0);

        // 一旦渲染完(使用完纹理单元,就要将激活的纹理单元恢复原状,即解绑纹理)
        for (GLuint i = 0; i < this->textures.size(); i++)
        {
            glActiveTexture(GL_TEXTURE0 + i);
            glBindTexture(GL_TEXTURE_2D, 0);
        }
    }

private:
    /*  缓冲数据:VAO VBO EBO  */
    GLuint VAO, VBO, EBO;

    /*  设置网格的缓冲数据,即设置VAO VBO EBO,并且将顶点和索引数据传入显存   */
    void setupMesh()
    {
        // 申请缓冲区对象的引用
        glGenVertexArrays(1, &this->VAO);
        glGenBuffers(1, &this->VBO);
        glGenBuffers(1, &this->EBO);

        // 将对象绑定到对应缓冲区类型上
        glBindVertexArray(this->VAO);
        glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
        
        // 将网格的顶点数据传入VBO中
        glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);

        // 绑定EBO并且将网格的索引顶点数据传入EBO中
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);

        // 设置顶点数据的解析方式(每次读取一个Vertex结构体即三个GL_FLOAT值)
        // 解析顶点位置数据
        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));

        // 设置完网格解绑VAO
        glBindVertexArray(0);
    }
};

 注意:编写这个程序时踩了好几个坑,所以我将它写在这里。
 第一个就是关于" aiString path"报错”未知重写说明符“,这个报错的原因是我们将自己写的头文件放在了标准头文件之前,解决方法就是把我们自己写的头文件引用写在引用头文件的最后。可参考C++未知重写说明符。
 第二个是关于stringstream的使用,可以参考stringstream简介。文章来源地址https://www.toymoban.com/news/detail-435370.html

到了这里,关于OpenGL加载模型 之网格的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FPGA基础知识点

    基础知识 逻辑值 逻辑0:表示低电平,也就是对应电路GND 逻辑1:表示高电平,也就是对应电路VCC 逻辑X:表示未知,有可能是高电平也有可能是低电平 逻辑Z:表示高阻态,外部没有激励信号,是一个悬空状态 数字进制格式 Verilog数字进制格式包括 二进制(b) , 八进制(

    2024年02月03日
    浏览(40)
  • 硬件基础知识点

    D:十进制 B:二进制 H:十六进制 二进制→十六进制 整数部分从右往左,小数部分从左往右。 四个二进制数看作一个十六进制数,不足的补零。 十六进制→二进制同理。 十进制→二进制 方法一:短除法 除二倒取整,乘二正取余 方法二:拆分法(二进制减法) 十进制数转

    2024年02月06日
    浏览(36)
  • 集合基础知识点

    当 Java 程序中需要存放数据的时候,通常会定义变量来实现数据的存储,但是,当需要存储大量数据的时候该怎么办呢?这时首先想到的是数组,但是!数组只能存放同一类型的数据,而且其长度是固定的,那怎么办了?集合便应运而生了。 Java 集合类存放在 java.util 包中,

    2024年02月03日
    浏览(42)
  • HTML基础知识点

    1、HTML基础 1.1、什么是网页?        网页是一个包含HTML标签的纯文本文件,它可以存放在世界某个角落的某一台计算机中,是万维网中的一页,是超文本标记语言格式。它通常是由图片、文字、链接、声音、视频等元素组成。通过网页浏览器访问。 1.2、什么是HTML?   

    2024年02月07日
    浏览(35)
  • Java 基础知识点

    Object 类相关方法   getClass 获取当前运行时对象的 Class 对象。 hashCode 返回对象的 hash 码。 clone 拷贝当前对象, 必须实现 Cloneable 接口。浅拷贝对基本类型进行值拷贝,对引用类型拷贝引用;深拷贝对基本类型进行值拷贝,对引用类型对象不但拷贝对象的引用还拷贝对象的相

    2024年02月13日
    浏览(47)
  • AI大模型知识点大梳理

    AI大模型是指具有巨大参数量的深度学习模型,通常包含数十亿甚至数万亿个参数。这些模型可以通过学习大量的数据来提高预测能力,从而在自然语言处理、计算机视觉、自主驾驶等领域取得重要突破。 AI大模型的定义具体可以根据参数规模来分类。根据OpenAI的分类方法,

    2024年02月09日
    浏览(40)
  • OSPF基础知识点2

    目录 OSPF的三张表: 邻居和邻接关系: OSPF邻居关系的建立过程: 邻接关系建立的详细过程: 指邻居: OSPF支持的网络类型:  OSPF路由器类型: OSPF有三张重要的表项,OSPF邻居表、LSDB表和OSPF路由表。 对于OSPF的邻居表,需要了解: 1.OSPF在传递链路状态信息之前,需先建立

    2024年02月09日
    浏览(33)
  • Python基础知识点入门

    初学Python时,以下是一些基础知识点和示例,以帮助你建立坚实的编程基础。 1. 变量和数据类型 Python中的变量用于存储数据。以下是一些常见的数据类型和示例: 整数(int) 浮点数(float) 字符串(str) 布尔值(bool) 2. 列表(List) 列表是一种有序的数据结构,可以存储

    2024年02月07日
    浏览(39)
  • Pytorch基础知识点复习

    本篇博客是本人对pytorch使用的查漏补缺,参考资料来自 深入浅出PyTorch,本文主要以提问的方式对知识点进行回顾,小伙伴们不记得的知识点可以查一下前面的教程哦。   现在并行计算的策略是 不同的数据分布到不同的设备中,执行相同的任务(Data parallelism) 。   它的逻

    2024年01月20日
    浏览(35)
  • 多线程基础知识点梳理

    进程(process):进程是计算机中的一个任务,比如打开浏览器、IntelliJ IDEA。 线程(thread):线程是进程内部的子任务。比如IDEA在敲代码的同时还能自动保存、自动导包,都是子线程做的。 进程和线程的关系就是一个进程包含一个或多个线程。 线程是操作系统调度的最小任

    2024年02月04日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包