OpenGL入门005——使用Shader类管理着色器

本节将把着色器有关的代码抽象出来为外部文件,通过OpenGL的API与着色器进行交互,从外部文件中加载着色器代码

一些概念

统一变量

概述: 在OpenGL着色器中,统一变量(unfiorm variable)是一种全局变量,可以从应用程序代码传递到着色器中,统一变量在着色器的整个生命周期内保持不变,通常用于传递一些不会频繁变化的数据,例如变换矩阵、材质属性、光源参数等

特点:

  • 全局性
  • 只读性
  • 应用程序设置

声明: 在着色器中,使用uniform关键字声明统一变量

实战

简介

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

目的: 学习如何使用Shader类来管理OpenGL的着色器

环境:

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

dependencies

shader.fs

作用: 着色器代码

1
2
3
4
5
6
7
8
9
10
11
// 指定OpenGL着色器语言的版本为3.30
#version 330 core
// 输入变量,表示传入的颜色值
in vec3 ourColor;
// 输出变量,表示片段的最终颜色
out vec4 FragColor;

void main() {
// 将输入的颜色值转换为vec4,并将alpha值设置为1.0,赋值给输出变量
FragColor = vec4(ourColor, 1.0f);
}

shader.vs

1
2
3
4
5
6
7
8
9
10
11
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 ourColor;

void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
ourColor = aColor;
}

utils

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

shader.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
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

using std::string;
using std::ifstream;
using std::stringstream;
using std::cout;
using std::endl;

class Shader {
public:
// 默认构造函数
Shader() {}
// 着色器程序ID
unsigned int ID;

// 构造函数
Shader(const char* vertexPath, const char* fragmentPath) {
string vertexCode;
string fragmentCode;
ifstream vShaderFile;
ifstream fShaderFile;

// 确保ifstream对象可以抛出异常
vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
try {
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
// 读取文件缓冲区内容到stream中
stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 将stream转换为字符串
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
} catch (ifstream::failure& e) {
cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 编译着色器
unsigned int vertex, fragment;
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// 删除着色器
glDeleteShader(vertex);
glDeleteShader(fragment);
}

// 激活着色器
void use() {
glUseProgram(ID);
}

// 实用的uniform工具函数
// 用于在着色器程序中设置uniform值
// 设置一个布尔类型的uniform变量
void setBool(const std::string& name, bool value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}

// 设置一个整型的uniform变量
void setInt(const std::string& name, int value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

// 设置一个浮点类型的uniform变量
void setFloat(const std::string& name, float value) const {
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}

// 设置一个vec2类型的uniform变量
void setVec2(const std::string& name, const glm::vec2& value) const {
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec2类型的uniform变量
void setVec2(const std::string& name, float x, float y) const {
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}

// 设置一个vec3类型的uniform变量
void setVec3(const std::string& name, const glm::vec3& value) const {
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec3类型的uniform变量
void setVec3(const std::string& name, float x, float y, float z) const {
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}

// 设置一个vec4类型的uniform变量
void setVec4(const std::string& name, const glm::vec4& value) const {
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec4类型的uniform变量
void setVec4(const std::string& name, float x, float y, float z, float w) {
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}

// 设置一个mat2类型的uniform变量
void setMat2(const std::string& name, const glm::mat2& mat) const {
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

// 设置一个mat3类型的uniform变量
void setMat3(const std::string& name, const glm::mat3& mat) const {
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

// 设置一个mat4类型的uniform变量
void setMat4(const std::string& name, const glm::mat4& mat) const {
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

private:
// 检查着色器编译/链接错误
void checkCompileErrors(GLuint shader, string type) {
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM") {
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << endl;
}
}
else {
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << endl;
}
}
}
};
#endif

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
27
28
29
#pragma once
#include<glad/glad.h>
#include"shader.h"

class RectangleModel {
public:
// 构造函数
RectangleModel(const Shader& shader);
// 析构函数
~RectangleModel();

// 绘制矩形
void draw();

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

// 着色器程序
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
#include "RectangleModel.h"
#include <iostream>

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

// 构造函数
RectangleModel::RectangleModel(const Shader& shader) : shader(shader) {
// 设置缓冲区
setElements();
}

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

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

/// private
void RectangleModel::setElements() {
// 顶点数据
float vertices[] = {
-0.75f, -0.75f, 1.0f, 0.0f, 0.0f,
0.75f, -0.75f, 1.0f, 0.5f, 0.0f,
-0.75f, 0.75f, 1.0f, 0.0f, 1.0f,
0.75f, 0.75f, 0.0f,1.0f,0.0f,
};
// 索引数据
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(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
// 启用顶点属性
glEnableVertexAttribArray(0);
// 设置顶点属性指针,颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));
// 启用顶点属性
glEnableVertexAttribArray(1);
}

main.cpp

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

int main() {
// 创建一个窗口Factory对象
GLFWWindowFactory myWindow(800, 600, "This is Title");
// 创建一个着色器对象
Shader shader("shader.vs", "shader.fs");
// 创建一个矩形模型对象
RectangleModel rectangle(shader);

// 运行窗口,传入一个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
23
24
25
26
27
28
29
30
31
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(Shader)

# 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)
find_package(glm CONFIG REQUIRED)

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

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

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

# 检查项目是否有dependeicies目录,如果存在,则在使用add_custom_command命令在构建后将dependencies目录中的文件复制到项目的输出目录
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies")
if(EXISTS ${SOURCE_DIR})
add_custom_command(TARGET Shader POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${SOURCE_DIR} $<TARGET_FILE_DIR:Shader>)
endif()

最终效果


OpenGL入门005——使用Shader类管理着色器
http://example.com/2024/11/03/OpenGL入门005——使用Shader类管理着色器/
作者
凌云行者
发布于
2024年11月3日
许可协议