OpenGL加载模型 之网格

news2024/11/17 7:33:05

基础知识点

  • 我们的工作就是去解析这些导出的模型文件,并将其中的模型数据存储为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中绘制物体的最小单位。一个模型通常有多个网格组成。

网格

  • 使用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简介。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/497178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【镜像取证篇】仿真碎片-记一次镜像仿真失败的复盘过程

【镜像取证篇】仿真碎片-记一次镜像仿真失败的复盘过程 这个是很久以前的一个镜像实验&#xff0c;当时仿真可以看到Windows的启动界面&#xff0c;但却一直无法正常进入系统&#xff0c;不断的尝试修复&#xff0c;都是显示错误&#xff0c;最后把类型改为IDE后&#xff0c;成…

ESP32-设备驱动TMP102数字温度传感器驱动

TMP102数字温度传感器驱动 文章目录 TMP102数字温度传感器驱动1、TMP102介绍2、硬件准备3、软件准备4、驱动实现1、TMP102介绍 TMP102 器件是一款数字温度传感器,非常适合需要高精度的 NTC/PTC 热敏电阻更换。 该器件提供 0.5C 的精度,无需校准或外部组件信号调理。 器件温度…

Spring Task

1.1介绍 Spring Task是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 定位&#xff1a;定时任务框架 作用&#xff1a;定时自动执行某段Java代码 为什么要在Java程序中使用Spring Task&#xff1f; 应用场景&#xff1a; 1).信用卡每…

C learning_11 (数组和在内存存储的理解、数组越界、数组作为形参)

目录 数组的理解 数组越界 数组作为函数参数 数组的理解 数组的含义 在C语言中&#xff0c;用于存储多个相同类型的元素。它可以被简单地定义为包含多个元素的容器。数组中每个元素都可以通过索引来访问&#xff0c;索引从零开始递增。 C语言中的数组可以包含任何基本数据类…

力扣---LeetCode141/142. 环形链表 (I)和(II) (代码详解+流程图+数学逻辑拓展)

文章目录 前言141. 环形链表 I1.1 链接&#xff1a;1.2 思路&#xff1a;1.3 代码&#xff1a;快慢指针1.4 流程图&#xff1a; 142. 环形链表 II2.1 链接&#xff1a;2.2 思路&#xff1a;2.3 代码&#xff1a;2.4 流程图&#xff1a; 拓展问题及证明(面试常问)&#xff1a;3.…

【雅特力】单片机AT32F421系列入门资料

1. 命名规则 AT32 全系列MCU选型手册.PDF AT32F421F8P7 AT32F421C8T7 (雅特力厂商送样的两个芯片版本) 2. 数据手册 【 数据手册】AT32F421系列引脚定义、电气特性与封装特性.PDF 3. 技术手册 【技术手册】AT32F421系列各外设(Peripheral)完整说明与各寄存器(Register)定…

[架构之路-190]-《软考-系统分析师》-4-据通信与计算机网络-5-图解CRC计算方法与步骤

目录 一、概述&#xff1a; 二、实战演示 假设&#xff1a; 第1步&#xff1a;把多项多项式转化为除数 第2步&#xff1a;把发送数据转换为被除数&#xff1a;在信息序列后加0 第3步&#xff1a;信息序列除以多项式序列 第4步&#xff1a;获得余数&#xff08;CRC校验值…

OpenCV4.x图像处理实例-搭建身份识别系统

搭建身份识别系统 文章目录 搭建身份识别系统1、人脸识别系统介绍2、人脸特征数据提取3、人脸识别模型训练4、从静态图像进行身份识别5、从视频流识别身份在本文中,将介绍如何使用 OpenCV 搭建一个人脸检测与身份识别系统。 为了构建我们的人脸识别系统,我们将首先执行人脸检…

对称加密与非对称加密、证书、SSL/TLS握手过程

文章目录 对称加密(Symmetrical Encryption)&#xff1a;非对称加密(Asymmetric Encryption)&#xff1a;区别&#xff1a;SSL证书TLS1.2握手过程 对称加密(Symmetrical Encryption)&#xff1a; 对称加密&#xff0c;是一种既简单速度又快的加密方式&#xff0c;加密与解密使用…

大数据之PySpark的RDD介绍

文章目录 前言一、RDD简介二、RDD的特性三、RDD的特点总结 前言 #博学谷IT学习技术支持# 之前的文章主要介绍Spark基础知识&#xff0c;例如集群角色、Spark集群运行流程等&#xff0c;接下来会进一步讨论Spark相对核心的知识&#xff0c;让我们拭目以待&#xff0c;同时也期待…

Linux安装Harbor亲测成功

Harbor简介 Harbor 是为企业用户设计的容器镜像仓库开源项目&#xff0c;包括了权限管理(RBAC)、LDAP、审计、安全漏洞扫描、镜像验真、管理界面、自我注册、HA 等企业必需的功能&#xff0c;同时针对中国用户的特点&#xff0c;设计镜像复制和中文支持等功能。 虽然Docker官方…

Android framework学习指南之Launcher启动过程原理分析

前言 Launcher是一个用来显示系统中已经安装的应用程序的应用程序&#xff0c;Launcher 在启动过程中会请求PackageManagerService 返回系统中已经安装的应用程序的信息&#xff0c;并将这些信息封装成一个快捷图标列表显示在系统屏幕上&#xff0c;这样用户可以通过点击这些快…

Flask框架的入门使用

Flask框架的入门使用 Flask框架Flask概述常用扩展包 Flask的基本使用环境准备创建helloworld.py文件启动运行访问 参数配置Flask对象配置应用程序配置读取配置信息加载配置信息配置对象与环境变量结合 app.run参数 开发服务器启动方式在命令行中运行Python程序中手动启动Pychar…

2.2.3开机流程中的BIOS与UEFI开机检测程序

操作系统的系统软件产生&#xff0c;是为了计算机所有硬件系统的资源合理分配。操作系统会控制所有的硬件并且提供核心功能&#xff0c;因此我们的计算机就能够认识硬盘内的文件系统&#xff0c;并且进一步的读取硬盘内的软件文件与执行该软件来达成各项软件的执行目的。 基本上…

【Redis】封装Redis缓存工具解决缓存穿透与缓存击穿问题

基于StringRedisTemplate封装一个缓存工具&#xff0c;主要有一下几个方法 方法1&#xff1a;将任意Java对象序列化为json并存储在String的指定key中且设置TTL 方法2&#xff1a;将任意Java对象序列化为json并存储在String的指定key中&#xff0c;并可以设置逻辑过期时间&…

【python可视化】常用数据类型

&#x1f64b;‍ 哈喽大家好&#xff0c;本次是python数据分析、挖掘与可视化专栏第二期 ⭐本期内容&#xff1a;常用数据类型 &#x1f3c6;系列专栏&#xff1a;Python数据分析、挖掘与可视化 &#x1f44d;欢迎大佬指正&#xff0c;一起学习&#xff0c;一起加油&#xff01…

【Frida-实战】EA游戏平台的文件监控(PsExec.exe提权)

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 代码编写开源代码搜索自己撸代码procexp确定句柄对应的文件名并过滤 2️⃣ PsExec.exe提权定位找不到EABackgroundService.exe的问题 PsExec.exe提权PsExec.exe原理 &#x1f6ec; 结论&#x1f4d6; 参考资料 &#x1f6eb; 问题…

4年Android开发,面试通过全靠狂刷这份面试题,从11K涨到25K+(内含答案)

在博主认为&#xff0c;对于Android面试以及进阶的最佳学习方法莫过于刷题博客书籍总结&#xff0c;前三者博主将淋漓尽致地挥毫于这篇博客文章中&#xff0c;至于总结在于个人&#xff0c;实际上越到后面你会发现面试并不难&#xff0c;其次就是在刷题的过程中有没有去思考&am…

【Python】序列类型③-集合

文章目录 1.集合(set)简介2.集合的定义3.集合的遍历4.集合的常用方法 1.集合(set)简介 集合是一种无序可变的容器对象 集合最大的特点:同一个集合内元素是不允许有重复的,因此集合自带"去重"效果 2.集合的定义 集合的定义有两种方式: 使用{}进行定义,这种方式不能定…