OpenGL入门003——使用Factory设计模式简化渲染流程

前面两节已经学会了如何使用opengl创建窗口并绘制三角形,我们可以看出有些步骤是固定的,而且都写在main.cpp,这一节我们将了解如何使用Factroy设计模型。将模型渲染逻辑封装在一个单独的类中,简化开发流程,且提高代码复用性。

一些概念

Factory设计模式

概述: 提供了一种将对象的实例化过程封装起来的方式, 使得客户端可以通过调用Factroy类的方法来创建对象。

作用:

  • 封装对象的创建过程,将对象的实例化过程封装在Factory类中,客户端可以通过调用工厂类的方法来创建对象,而无需知道对象的具体实现细节
  • 隐藏对象的创建逻辑,从而实现对象的创建逻辑和客户端代码的分离
  • 提高代码的可维护性和可扩展性以及复用性

实战

简介

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

目的: 使用Factory设计模式绘制一个三角形

环境:

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

utils

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

windowFactory.h

作用: 实现创建窗口对象的Factory类

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;
};

TriangleModel.h

作用: 三角模型的头文件

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

class TriangleModel {
public:
// 默认构造函数
TriangleModel();
// 默认析构函数
~TriangleModel();

// 绘制三角形
void draw();

private:
unsigned int VAO;
unsigned int VBO;
// 着色器程序
unsigned int shaderProgram;

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

TriangleMode.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
#include "TriangleModel.h"
#include <iostream>

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

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

// 默认构造函数
TriangleModel::TriangleModel() {
// 编译着色器
compileShaders();
// 设置缓冲区
setupBuffers();
}

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

/// public
// 绘制三角形
void TriangleModel::draw() {
// 使用着色器程序
glUseProgram(this->shaderProgram);
// 绑定VAO
glBindVertexArray(this->VAO);
// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
}

/// private
// 编译着色器
void TriangleModel::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 TriangleModel::setupBuffers() {
// 顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,

1.0f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.25f, 0.0f, 0.0f
};

// 生成一个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);

// 定义顶点属性的布局
// - 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/TriangleModel.h"
#include "utils/windowFactory.h"

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

// 创建一个三角形模型对象
TriangleModel triangle;

// 运行窗口,传入一个lambda表达式,用于自定义渲染逻辑
myWindow.run([&]() {
// 绘制三角形
triangle.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入门003——使用Factory设计模式简化渲染流程
http://example.com/2024/11/01/OpenGL入门003——使用Factory设计模式简化渲染流程/
作者
凌云行者
发布于
2024年11月1日
许可协议