OpenGL教程:4. 手写一个Shader类

在前面的教程中,我们的着色器都是以const char的数组形式来传递的。但是这有个很严重的问题,非常不方面着色器的编写!后期的学习中,我们大部份都需要编写很多着色器的代码,如果在这种环境下编写会非常不方便的。所以,我们可以在另外一个文本中编写着色器,然后用C++的输入流来读取这个文本(这个文本的格式可以是txt,也可以是其他任何格式,但为了方便管理,推荐使用glsl格式或者vsh和fsh格式)。看来写一个Shader读取编译器势在必行。

并且,为了方便管理,我们最好写一个Shader类,专门管理着色器。

定义Shader类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Shader{
public:
//着色器id
unsigned int id;

//构造函数,读取着色器并编译。接受两个参数,分别为顶点着色器和片元着色器的路径
Shader(const std::string &vertexShaderPath, const std::string &fragmentShaderPath);
//析构函数,回收内存
~Shader();
//使用着色器的函数
void use();
//Uniform变量设置函数
void setBool(const std::string &name, bool value) const;
void setInt(const std::string &name, int value) const;
void setFloat(const std::string &name, float value) const;
void setVector3(const std::string &name, float x, float y, float z) const;
void setVector3(const std::string &name, glm::vec3 vec) const;
void setVector4(const std::string &name, float x, float y, float z, float w) const;
void setVector4(const std::string &name, glm::vec4 vec) const;
void setMatrix3(const std::string &name, glm::mat3 mat3) const;
void setMatrix4(const std::string &name, glm::mat4 mat4) const;
};

Shader类的构造函数:利用C++输入文件流读取着色器并编译之

C++的输入文件流位于ifstream头文件中,所以要使用他,先使用头文件:

1
#include <fstream>

定义构造函数:

1
2
3
Shader::Shader(const std::string &vertexShaderPath, const std::string &fragmentShaderPath) {
...
}

先定义两个着色器的代码字符串以及其路径变量:

1
2
3
4
5
// 读取文件
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;

接下来读取两个着色器的代码(用try…catch环绕读取主体):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
// 打开文件
vShaderFile.open(vertexShaderPath);
fShaderFile.open(fragmentShaderPath);
std::stringstream vShaderStream, fShaderStream;
// 读取文件的缓冲内容到数据流中
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 转换数据流到string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
} catch (std::ifstream::failure e) {
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ\n";
}

这个时候vertexCode和fragmentCode就有内容了,但是glad不认string,所以要转成char*。

1
2
3
//方便起见,就另外建立两个变量char*
const char *vShaderCode = vertexCode.c_str();
const char *fShaderCode = fragmentCode.c_str();

接下来就是简单的编译着色器并链接啦:

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
// 编译着色器
unsigned int vertex, fragment;
int success;
char infoLog[512];
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, nullptr);
glCompileShader(vertex);
// 打印错误信息
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertex, 512, nullptr, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, nullptr);
glCompileShader(fragment);
// 打印编译错误信息(如果有的话)
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragment, 512, nullptr, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}

// 链接着色器程序,这里注意要给id赋值
this->id = glCreateProgram();
glAttachShader(id, vertex);
glAttachShader(id, fragment);
glLinkProgram(id);
// 打印链接错误(如果有的话)
glGetProgramiv(id, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(id, 512, nullptr, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}

// 删除着色器,因为已经链接到程序了
glDeleteShader(vertex);
glDeleteShader(fragment);

Shader类的析构函数:回收内存

函数内容非常简答,就是删除着色器程序而已:

1
2
3
Shader::~Shader() {
glDeleteProgram(this->id);
}

Shader类的Uniform变量设置函数

这里不多解释啦,非常简单

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
void Shader::setBool(const std::string &name, bool value) const {
glUniform1i(glGetUniformLocation(id, name.c_str()), (int) value);
}

void Shader::setInt(const std::string &name, int value) const {
glUniform1i(glGetUniformLocation(id, name.c_str()), value);
}

void Shader::setFloat(const std::string &name, float value) const {
glUniform1f(glGetUniformLocation(id, name.c_str()), value);
}

void Shader::setVector3(const std::string &name, float x, float y, float z) const {
glUniform3fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(glm::vec3(x, y, z)));
}

void Shader::setVector3(const std::string &name, glm::vec3 vec) const {
glUniform3fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(vec));
}

void Shader::setVector4(const std::string &name, float x, float y, float z, float w) const {
glUniform4fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(glm::vec4(x, y, z, w)));
}

void Shader::setVector4(const std::string &name, glm::vec4 vec) const {
glUniform4fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(vec));
}

void Shader::setMatrix3(const std::string &name, glm::mat3 mat3) const {
glUniformMatrix3fv(glGetUniformLocation(id, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat3));
}

void Shader::setMatrix4(const std::string &name, glm::mat4 mat4) const {
glUniformMatrix4fv(glGetUniformLocation(id, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat4));
}

Shader类的use函数

在使用着色器的时候,直接用use函数来代替glUseProgram函数啦:

1
2
3
void Shader::use() const {
glUseProgram(id);
}

这样我们的Shader类就写好了!

使用Shader类

我们利用Shader类,再次绘制一个三角形吧!代码不多解释了:

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
//
// Created by inver on 2022/9/12.
//

#include <iostream>
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "Shader/Shader.h"

int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

GLFWwindow *window = glfwCreateWindow(800, 600, "三角形", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Error: Fail to create window! \n";
glfwTerminate();
return -1;
}

glfwMakeContextCurrent(window);

if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
std::cout << "Error: Fail to initialize GLAD! \n";
return -1;
}

//End initialize glfwSource and glad

float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};

//initialize vbo
unsigned int vbo;
glGenBuffers(1, &vbo); //spawn a new vbo
glBindBuffer(GL_ARRAY_BUFFER, vbo); //bind vbo with ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices,
GL_STATIC_DRAW); //transport vertices data to buffer memory
//GL_STATIC_DRAW means static data (not dynamic)

//initialize vao
unsigned int vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

//set vertices pointer
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) nullptr);
glEnableVertexAttribArray(0);


//initialize shader
std::string vShaderPath = "shaders/HelloTriangle/triangle.vsh";
std::string fShaderPath = "shaders/HelloTriangle/triangle.fsh";

Shader shader(vShaderPath, fShaderPath);

while (!glfwWindowShouldClose(window)) {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

shader.use();
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3);

glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
}
Author

InverseDa

Posted on

2022-11-03

Updated on

2023-03-30

Licensed under

Comments

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×