1. 引言

本文基于C++语言,描述OpenGL的Mesh

前置知识可参考:

笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:

2. 概述

3D建模工具(3D Modeling Tool)会在导出到模型文件的时候自动生成所有的顶点坐标、顶点法线以及纹理坐标

所以,解析这些导出的模型文件以及提取所有有用的信息,将它们储存为OpenGL能够理解的格式,就可以进行绘制

Assimp是一个非常流行的模型导入库,能够导入很多种不同的模型文件格式(并也能够导出部分的格式),会将所有的模型数据加载至Assimp的通用数据结构中

Assimp的GitHub站点为:assimp/assimp: The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clean data structure. (github.com)

Assimp的指导手册:The Asset-Importer-Lib Documentation — Asset-Importer-Lib December 2020 documentation (assimp-docs.readthedocs.io)

3. 构建Assimp

参考官方构建手册:assimp/Build.md at master · assimp/assimp (github.com)

笔者使用CMake在Linux上构建

拉取源码:

git clone https://github.com/assimp/assimp.git

CMake构建:

cd assimp
cmake CMakeLists.txt

编译:

make

安装:

sudo make install

4. 编写Mesh类

一个完整的模型往往是由多个组件构成,单个的组件称之为Mesh

多个Mesh组成Model,在这里,先定义Mesh类

定义顶点:

struct Vertex {
glm::vec3 Position;
glm::vec3 Normal;
glm::vec2 TexCoords;
};

定义纹理:

struct Texture {
unsigned int id;
string type;
};

定义Mesh类:

class Mesh {
public:
/* 网格数据 */
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
/* 函数 */
Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures);
void Draw(Shader shader);
private:
/* 渲染数据 */
unsigned int VAO, VBO, EBO;
/* 函数 */
void setupMesh();
};

构造函数:

Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures; setupMesh();
}

初始化Mesh:

void setupMesh()
{
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO); glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),
&indices[0], GL_STATIC_DRAW); // 顶点位置
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
// 顶点法线
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
// 顶点纹理坐标
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); glBindVertexArray(0);
}

渲染函数:

void Draw(Shader shader)
{
unsigned int diffuseNr = 1;
unsigned int specularNr = 1;
for(unsigned int i = 0; i < textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i); // 在绑定之前激活相应的纹理单元
// 获取纹理序号(diffuse_textureN 中的 N)
string number;
string name = textures[i].type;
if(name == "texture_diffuse")
number = std::to_string(diffuseNr++);
else if(name == "texture_specular")
number = std::to_string(specularNr++); shader.setInt(("material." + name + number).c_str(), i);
glBindTexture(GL_TEXTURE_2D, textures[i].id);
}
glActiveTexture(GL_TEXTURE0); // 绘制网格
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}

Mesh类的调用见:基于C++的OpenGL 14 之模型加载 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

5. 完整代码

主要文件mesh.hpp

#ifndef MESH_HPP
#define MESH_HPP #include <glad/glad.h> // holds all OpenGL type declarations
#include <glm.hpp> #include "Shader.hpp" #include <string>
#include <vector> using namespace std; struct Vertex {
glm::vec3 Position;
glm::vec3 Normal;
glm::vec2 TexCoords;
};
struct Texture {
unsigned int id;
string type;
};
class Mesh {
public:
/* 网格数据 */
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
/* 函数 */
Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures; setupMesh();
} void Draw(Shader shader)
{
unsigned int diffuseNr = 1;
unsigned int specularNr = 1;
for(unsigned int i = 0; i < textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i); // 在绑定之前激活相应的纹理单元
// 获取纹理序号(diffuse_textureN 中的 N)
string number;
string name = textures[i].type;
if(name == "texture_diffuse")
number = std::to_string(diffuseNr++);
else if(name == "texture_specular")
number = std::to_string(specularNr++); shader.setInt(("material." + name + number).c_str(), i);
glBindTexture(GL_TEXTURE_2D, textures[i].id);
}
glActiveTexture(GL_TEXTURE0); // 绘制网格
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
} private:
/* 渲染数据 */
unsigned int VAO, VBO, EBO;
/* 函数 */
void setupMesh()
{
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO); glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),
&indices[0], GL_STATIC_DRAW); // 顶点位置
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
// 顶点法线
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
// 顶点纹理坐标
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); glBindVertexArray(0);
}
}; #endif

着色器shader.hpp

#ifndef SHADER_HPP
#define SHADER_HPP #include <glad/glad.h>
#include <glm/glm.hpp> #include <string>
#include <fstream>
#include <sstream>
#include <iostream> class Shader
{
public:
unsigned int ID;
// constructor generates the shader on the fly
// ------------------------------------------------------------------------
Shader(const char* vertexPath, const char* fragmentPath)
{
// 1. retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// ensure ifstream objects can throw exceptions:
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
try
{
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char * fShaderCode = fragmentCode.c_str();
// 2. compile shaders
unsigned int vertex, fragment;
// vertex shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// fragment Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// shader Program
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// delete the shaders as they're linked into our program now and no longer necessery
glDeleteShader(vertex);
glDeleteShader(fragment); }
// activate the shader
// ------------------------------------------------------------------------
void use() const
{
glUseProgram(ID);
}
// utility uniform functions
// ------------------------------------------------------------------------
void setBool(const std::string &name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
// ------------------------------------------------------------------------
void setInt(const std::string &name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setFloat(const std::string &name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setVec2(const std::string &name, const glm::vec2 &value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string &name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void setVec3(const std::string &name, const glm::vec3 &value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string &name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void setVec4(const std::string &name, const glm::vec4 &value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string &name, float x, float y, float z, float w) const
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void setMat2(const std::string &name, const glm::mat2 &mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string &name, const glm::mat3 &mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string &name, const glm::mat4 &mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
} private:
// utility function for checking shader compilation/linking errors.
// ------------------------------------------------------------------------
void checkCompileErrors(GLuint shader, std::string type)
{
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
else
{
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
}
};
#endif

6. 参考资料

[1]Assimp - LearnOpenGL CN (learnopengl-cn.github.io)

[2]网格 - LearnOpenGL CN (learnopengl-cn.github.io)

[3]assimp/Build.md at master · assimp/assimp (github.com)

基于C++的OpenGL 13 之Mesh的更多相关文章

  1. 基于Cocos2d-x学习OpenGL ES 2.0之多纹理

    没想到原文出了那么多错别字,实在对不起观众了.介绍opengl es 2.0的不多.相信介绍基于Cocos2d-x学习OpenGL ES 2.0之多纹理的,我是独此一家吧.~~ 子龙山人出了一个系列: ...

  2. 基于Cocos2d-x学习OpenGL ES 2.0系列——纹理贴图(6)

    在上一篇文章中,我们介绍了如何绘制一个立方体,里面涉及的知识点有VBO(Vertex Buffer Object).IBO(Index Buffer Object)和MVP(Modile-View-P ...

  3. 基于Cocos2d-x学习OpenGL ES 2.0系列——使用VBO索引(4)

    在上一篇文章中,我们介绍了uniform和模型-视图-投影变换,相信大家对于OpenGL ES 2.0应该有一点感觉了.在这篇文章中,我们不再画三角形了,改为画四边形.下篇教程,我们就可以画立方体了, ...

  4. 1、基于MFC的OpenGL程序

    首先,使用的库是GLUT以及GLAUX,先下载两者,添加查找路径以及链接   一.单文本文件   工程openGLMFC 1.创建单文本文件   2.添加路径.链接 方法如之前篇章所示, 链接库为op ...

  5. 【游戏开发】基于VS2017的OpenGL开发环境搭建

    一.简介 最近,马三买了两本有关于“计算机图形学”的书籍,准备在工作之余鼓捣鼓捣图形学和OpenGL编程,提升自己的价值(奔着学完能涨一波工资去的).俗话说得好,“工欲善其事,必先利其器”.想学习图形 ...

  6. 基于MFC的OpenGL程序<转>

    原贴地址:https://www.cnblogs.com/pinking/p/6180225.html 首先,使用的库是GLUT以及GLAUX,先下载两者,添加查找路径以及链接   一.单文本文件   ...

  7. 基于Cocos2d-x学习OpenGL ES 2.0系列——OpenGL ES渲染之Shader准备(7)

    Cocos2d-x底层图形绘制是使用OpenGL ES协议的.OpenGL ES是什么呢? OpenGL ES(OpenGl for Embedded System)是OpenGL三维图形API的子集 ...

  8. 基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个立方体(5)

    在上篇文章中,我们介绍了VBO索引的使用,使用VBO索引可以有效地减少顶点个数,优化内存,提高程序效率. 本教程将带领大家一起走进3D--绘制一个立方体.其实画立方体本质上和画三角形没什么区别,所有的 ...

  9. 基于对话框的Opengl框架

    转自:http://blog.csdn.net/longxiaoshi/article/details/8238933 12-11-29 14:55 1198人阅读 评论(6) 收藏 举报  分类: ...

  10. 基于事件驱动机制,在Service Mesh中进行消息传递的探讨

    翻译 | 宋松 原文 | https://www.infoq.com/articles/service-mesh-event-driven-messaging 关键点 当前流行的Service Mes ...

随机推荐

  1. 【实时数仓】Day01-数据采集层:数仓分层、实时需求、架构分析、日志数据采集(采集到指定topic和落盘)、业务数据采集(MySQL-kafka)、Nginx反向代理、Maxwell、Canel

    一.数仓分层介绍 1.实时计算与实时数仓 实时计算实时性高,但无中间结果,导致复用性差 实时数仓基于数据仓库,对数据处理规划.分层,目的是提高数据的复用性 2.电商数仓的分层 ODS:原始日志数据和业 ...

  2. kubernetes CKA题库(附答案)

    第一题 RBAC授权问题权重: 4% 设置配置环境:[student@node-1] $ kubectl config use-context k8s Context为部署管道创建一个新的Cluste ...

  3. C#不提升自己程序的权限实现操作注册表

    1. 绪论 当我们编写了自己的C#程序,有程序自定义的文件类型时,通常希望它满足以下需求: 双击自定义文件打开自定义程序 自定义文件有着自己的图标 此时,在网上检索可以发现,大多数回答是使用Micro ...

  4. 模型驱动设计的构造块(上)——DDD

    为了保证软件实践得简洁并且与模型保持一致,不管实际情况如何复杂,必须运用建模和设计的实践. 某些设计决策能够使模型和程序紧密结合在一起,互相促进对方的效用.这种结合要求我们注意每个元素的细节,对细节问 ...

  5. Vue 响应式原理模拟以及最小版本的 Vue的模拟

    在模拟最小的vue之前,先复习一下,发布订阅模式和观察者模式 对两种模式有了了解之后,对Vue2.0和Vue3.0的数据响应式核心原理 1.Vue2.0和Vue3.0的数据响应式核心原理 (1).  ...

  6. 深入探究Java中的对象类型变量声明操作——在声明对象时,系统究竟做了什么?

    深入探究Java中的对象类型变量声明操作--在声明对象时,系统究竟做了什么? 摘要:本文主要对Java中的对象类型变量的声明的底层原理做了探究. 目录 深入探究Java中的对象类型变量声明操作--在声 ...

  7. java入门与进阶-P1.5+P1.6

    输入 Scanner输入语句介绍 java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入. 下面是创建 Scanner 对象的基本语法: Sca ...

  8. 继承QAbstractTableModel QStyledItemDelegate实现自定义表格,添加进度条和选中框。

    由于项目要求,需要实现一个列表目录显示信息,并且需要实现每一项提供进度条和选项框功能,所以需要继承QAbstractTableModel和QStyledItemDelegate进行自定义. -自定义数 ...

  9. Appium资源汇总

    Appium资源汇总 created: 2022-8-4 软件 网址 说明 https://github.com/appium/appium-inspector/releases 检查器的下载路径 h ...

  10. rust 网上资料记录(自用)

    最近要学嵌入式的rust,记录一些资料的url,方便自己查阅 书籍 常用的: rust圣经(不是权威指南那本)https://course.rs/ 中文 rust 参考手册 https://rustw ...