OpenGL入门004——使用EBO绘制矩形

hexo clean
hexo s
本节将利用EBO来绘制矩形

一些概念

EBO

概述: Element Buffer Object 用于存储顶点的索引数据,以便在绘制图形时可以重用顶点数据,从而减少内存使用和提供高性能

使用步骤:

  1. 定义顶点和索引数据:
    • 顶点数据包含图形的顶点坐标
    • 索引数据定义了绘制图形的顺序
  2. 生成和绑定VAO
  3. 生成和绑定VBO
  4. 传递顶点数据到VBO
  5. 生成和绑定EBO:
    • 使用glGenBuffers生成EBO
    • 使用glBindBuffer绑定EBO
  6. 传递索引数据到EBO:使用glBufferData将索引数据传递到EBO
  7. 绘制图形:使用glDrawElements函数,根据EBO中的索引数据绘制图形

实战

简介

怎么在vscode上使用cmake构建项目,具体可以看这篇Windows上如何使用CMake构建项目 - 凌云行者的博客

目的: 利用EBO绘制一个矩形

环境:

  • 编译工具链:使用msys2安装的mingw-gcc
  • 依赖项:glfw3:x64-mingw-static,glad:x64-mingw-static(通过vcpkg安装)

utils

创建utils目录,将windowFactory.h,RectangleModel.h,RectangleModel.cpp文件放到这个目录下面

windowFactory.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#pragma once
#include <glad/glad.h> // gald前面不能包含任何opengl头文件
#include <GLFW/glfw3.h>
#include <functional>
#include <iostream>

using std::cout;
using std::endl;

class GLFWWindowFactory {
public:
// 默认构造函数
GLFWWindowFactory() {}
// 构造函数,初始化窗口
GLFWWindowFactory(int width, int height, const char* title) {
// 初始化glfw
glfwInit();
// 设置opengl版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 使用核心模式:确保不使用任何被弃用的功能
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

// 创建glfw窗口
this->window = glfwCreateWindow(width, height, title, NULL, NULL);
if (this->window == NULL) {
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
exit(-1);
}
// 设置当前窗口的上下文
glfwMakeContextCurrent(this->window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(this->window, framebuffer_size_callback);
// 加载所有opengl函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
cout << "Failed to initialize GLAD" << endl;
}
// 再次设置当前窗口的上下文,确保当前上下文仍然是刚刚创建的窗口,是一个安全措施
glfwMakeContextCurrent(this->window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(this->window, framebuffer_size_callback);
}

// 获取窗口对象
GLFWwindow* getWindow() {
return this->window;
}

// 运行窗口,传入一个自定义的更新函数
void run(std::function<void()> updateFunc) {
// 启用深度测试,opengl将在绘制每个像素之前比较其深度值,以确定该像素是否应该被绘制
glEnable(GL_DEPTH_TEST);

// 循环渲染
while (!glfwWindowShouldClose(this->window)) { // 检查是否应该关闭窗口
// 清空屏幕所用的颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 清空颜色缓冲,主要目的是为每一帧的渲染准备一个干净的画布
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 处理输入
GLFWWindowFactory::process_input(this->window);

// 执行更新函数
updateFunc();

// 交换缓冲区
glfwSwapBuffers(this->window);
// 处理所有待处理事件,去poll所有事件,看看哪个没处理的
glfwPollEvents();
}

// 终止GLFW,清理GLFW分配的资源
glfwTerminate();
}

// 窗口大小改变的回调函数
static void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
// 确保视口与新窗口尺寸匹配,注意在视网膜显示器上,宽度和高度会显著大于指定值
glViewport(0, 0, width, height);
}

// 处理输入
static void process_input(GLFWwindow* window) {
// 按下ESC键时进入if块
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
// 关闭窗口
glfwSetWindowShouldClose(window, true);
}

private:
// 窗口对象
GLFWwindow* window;
};

RectangleModel.h

作用: 矩形模型的头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma once
#include<glad/glad.h>

class RectangleModel {
public:
// 构造函数
RectangleModel();
// 析构函数
~RectangleModel();

// 绘制矩形
void draw();

private:
unsigned int VAO;
unsigned int VBO;
unsigned int EBO;

// 着色器程序
unsigned int shaderProgram;

// 编译着色器
void compileShaders();
// 设置缓冲区
void setElements();
}

RectangleModel.cpp

作用: 三角形模型的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "RectangleModel.h"
#include <iostream>

using std::cout;
using std::endl;

// 顶点属性位置
const int VERTEX_ATTR_POSITION = 0;
// 每个顶点的组件数
const int NUM_COMPONENTS_PER_VERTEX = 2;

// 构造函数
RectangleModel::RectangleModel() {
// 编译着色器
compileShaders();
// 设置缓冲区
setElements();
}

// 析构函数
RectangleModel::~RectangleModel() {
// 删除VAO
glDeleteVertexArrays(1, &this->VAO);
// 删除VBO
glDeleteBuffers(1, &this->VBO);
// 删除EBO
glDeleteBuffers(1, &this->EBO);
}

/// public
// 绘制矩形
void RectangleModel::draw() {
// 使用着色器程序
glUseProgram(shaderProgram);
// 绑定VAO
glBindVertexArray(VAO);
// 绘制矩形,即绘制两个三角形,GL_UNSIGNED_INT表示索引数组中的每个元素都是一个无符号整数
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

/// private
// 编译着色器
void RectangleModel::compileShaders() {
// 顶点着色器源码
const char* vertexShaderSource = "#version 330 core\n" // 指定了GLSL(OpenGL着色器语言)的版本
"layout (location = 0) in vec3 aPos;\n" // 定义了一个输入变量aPos,它是一个vec3类型的变量, 并且指定了它的位置值为0, 这意味着顶点属性数组的第一个属性将被绑定到这个变量
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // 将输入的顶点位置aPos转换为一个四维向量,gl_Postion是OpengGL固定功能管线中用于存储顶点位置的变量
"}\0";

// 片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n" // 指定了GLSL(OpenGL着色器语言)的版本
"out vec4 FragColor;\n" // 定义了一个输出变量FragColor,它是一个vec4类型的变量,表示片段颜色,out关键字表示这个变量将输出到渲染管线的下一个阶段
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" // 将输出颜色设置为橙色
"}\n\0";

// 构建并编译顶点着色程序
// 创建一个着色器对象,GL_VERTEX_SHADER表示顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
// 将着色器源码附加到着色器对象上
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
// 编译着色器
glCompileShader(vertexShader);
// 检查着色器是否编译成功
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
<< infoLog << endl;
}
// 构建并编译片段着色器
// 创建一个着色器对象,GL_FRAGMENT_SHADER表示片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// 将着色器源码附加到着色器对象上
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
// 编译着色器
glCompileShader(fragmentShader);
// 检查着色器是否编译成功
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
<< infoLog << endl;
}
// 创建着色器程序对象
this->shaderProgram = glCreateProgram();
// 将着色器对象附加到着色器程序上
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
// 链接程序对象
glLinkProgram(shaderProgram);
// 检查链接是否成功
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
<< infoLog << endl;
}
// 删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}

void RectangleModel::setElements() {
// 顶点数据
float vertices[] = {
-0.75f, -0.75f,
0.75f, -0.75f,
-0.75f, 0.75f,
0.75f, 0.75f
};
// 索引数据
int indices[] = {
0, 1, 2,
1, 2, 3, };

// 生成一个VAO
glGenVertexArrays(1, &this->VAO);
// 绑定VAO,使其成为当前操作的VAO
glBindVertexArray(this->VAO);
// 生成一个VBO
glGenBuffers(1, &this->VBO);
// 绑定VBO, 使其成为当前操作的VBO,GL_ARRAY_BUFFER表示顶点缓冲区
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
// 为当前绑定的VBO创建并初始化数据存储,GL_STATIC_DRAW表示数据将一次性提供给缓冲区,并且在之后的绘制过程中不会频繁更改
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 生成一个EBO
glGenBuffers(1, &this->EBO);
// 绑定EBO,使其成为当前操作的EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 传递索引数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 定义顶点属性的布局
// - index:顶点属性的索引
// - size:每个顶点属性的数量,每个顶点有三个分享
// - type:数据类型
// - normalized:是否将非浮点数值归一化
// - stride:连续顶点属性之间的间隔
// - pointer:数据在缓冲区中的偏移量
glVertexAttribPointer(VERTEX_ATTR_POSITION, NUM_COMPONENTS_PER_VERTEX, GL_FLOAT, GL_FALSE, NUM_COMPONENTS_PER_VERTEX * sizeof(float), (void*)0);
// 启用顶点属性数组
glEnableVertexAttribArray(VERTEX_ATTR_POSITION);
}

main.cpp

作用: 程序入口点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "utils/RectangleModel.h"
#include "utils/windowFactory.h"

int main() {
// 创建一个窗口Factory对象
GLFWWindowFactory myWindow(800, 600, "This is Title");

// 创建一个矩形模型对象
RectangleModel rectangle;

// 运行窗口,传入一个lambda表达式,用于自定义渲染逻辑
myWindow.run([&]() {
// 绘制矩形
rectangle.draw();
});

return 0;
}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(HelloFactory)

# vcpkg集成, 这里要换成你自己的vcpkg工具链文件和共享库路径
set(VCPKG_ROOT D:/software6/vcpkg/)
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
set(CMAKE_PREFIX_PATH ${VCPKG_ROOT}/installed/x64-mingw-static/share)

# 查找所需的包
find_package(glad CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)

# 搜索并收集utils文件夹下的所有源文件
file(GLOB UTILS "utils/*.cpp", "utils/*.h")

# 添加可执行文件(还要加入utils文件夹下的源文件)
add_executable(HelloFactory main.cpp ${UTILS})

# 链接所需的库
target_link_libraries(HelloFactory PRIVATE glad::glad glfw)

最终效果


OpenGL入门004——使用EBO绘制矩形
http://example.com/2024/11/01/OpenGL入门004——使用EBO绘制矩形/
作者
凌云行者
发布于
2024年11月1日
许可协议