此篇博文为个人OpenGL学习笔记,内容参考LearnOpenGL CNGLFW官方文档OpenGL 4 Reference Pages、OpenGL编程指南(原书第9版)

现代OpenGL渲染管线严重依赖着色器来处理传入的数据。

无论是OpenGL还是其它图形API的着色器,通常都是通过一种特殊的编程语言去编写的。对于OpenGL来说使用GLSL,也就是OpenGL Shading Language。

着色器程序和C程序类似都从main()函数开始执行:

#version 460 core

void main() {
    // code
}

//为注释符号,到当前行末结束。此外着色器程序也支持/*和*/多行注释。但这里main()函数不需要返回整数值,它被声明为void

GLSL是一种强类型语言,所有变量都必须事先声明并定义变量类型。GLSL中包含C等其它语言大部分的默认基础数据类型:

类型描述
floatIEEE 32位浮点数
doubleIEEE 64位浮点数
int有符号的二进制补码的32位整数
uint无符号的32位整数
bool布尔值

可以在使用变量之前任何时候声明变量,GLSL变量作用域规则:

  • 在任何函数定义之外声明的变量拥有全局作用域,因此对着色器程序中的所有函数都是可见的。
  • 在一组大括号内(例如函数定义、循环或者if引领的代码块等)声明的变量,只能在大括号的范围内存在。
  • 循环的迭代自变量只能在循环体内起作用。

所有变量都必须在声明的同时进行初始化:

int i, numParticles = 1500;
float force, g = -9.8f;
bool falling = true;
double pi = 3.1415926535897932384626LF;

GLSL支持的数值隐式转换更少一些:

所需的类型可以从这些类型隐式转换
uintint
floatintuint
doubleintuintfloat

但它们可以使用构造函数进行互相转换,例如:

float f = 10.0f;
int ten = int(f);

GLSL的操作类型可以进行合并,从而与核心OpenGL的数据类型相匹配,以简化计算过程的操作。

GLSL支持2个、3个以及4个分量的向量,每个分量都可以使用各种基本类型。GLSL也支持floatdouble类型的矩阵:

基本类型2D向量3D向量4D向量矩阵类型
floatvec2vec3vec4mat2 mat3 mat4 mat2×2 mat2×3 mat2×4 mat3×2 mat3×3 mat3×4 mat4×2 mat4×3 mat4×4
doubledvec2dvec3dvec4dmat2 dmat3 dmat4 dmat2×2 dmat2×3 dmat2×4 dmat3×2 dmat3×3 dmat3×4 dmat4×2 dmat4×3 dmat4×4
intivec2ivec3ivec4-
uintuvec2uvec3uvec4-
boolbvec2bvec3bvec4-

矩阵类型需要给出两个维度信息,其中第一个值表示列数第二个值表示行数。

使用这些类型声明的变量的初始化过程与它们的标量部分是类似的:

vec3 velocity = vec3(0.0f, 2.0f, 3.0f);

类型之间也可以进行等价的转换:

ivec3 steps = ivec3(velocity);

向量的构造函数还可以用来截短或者加长一个向量。如果将一个较长的向量传递给一个很较短的向量的构造函数,那么向量将被自动截短到对应的长度。

vec4 color;
vec3 RGB = vec3(color); //RGB只有三个分量

类似地,也可以使用同样的方式来加长一个向量。

vec3 white = vec3(1.0f); //white = (1.0f, 1.0f, 1.0f);
vec4 translucent = vec4(white, 0.5f);

矩阵的构造方式与此相同,并且可以将它初始化为一个对角矩阵或者完全填充的矩阵。对于对角矩阵,只需要向构造函数传递一个值,矩阵的对角线元素就设置为此值,其它元素全部设置为0:

$$ m=mat3(4.0f)= \left( \begin{matrix} 4.0 & 0.0 & 0.0 \\ 0.0 & 4.0 & 0.0 \\ 0.0 & 0.0 & 4.0 \\ \end{matrix} \right) $$

矩阵也可以通过在构造函数中指定每一个元素来构建。传入元素可以是标量和向量的集合。矩阵的指定需遵循列主序的原则,传入的数据将首先填充列然后填充行。

例如可以通过下面几种形式初始化一个$3 \times 3$的矩阵:

mat3 M = mat3(1.0f, 2.0f, 3.0f,
              4.0f, 5.0f, 6.0f,
              7.0f, 8.0f, 9.0f);

vec3 column1 = vec3(1.0f, 2.0f, 3.0f);
vec3 column2 = vec3(4.0f, 5.0f, 6.0f);
vec3 column3 = vec3(7.0f, 8.0f, 9.0f);

mat3 M = mat3(column1, column2, column3);

甚至是

vec3 column1 = vec3(1.0f, 2.0f);
vec3 column2 = vec3(4.0f, 5.0f);
vec3 column3 = vec3(7.0f, 8.0f);

mat3 M = mat3(column1, 3.0f,
              column2, 6.0f,
              column3, 9.0f);

得到的结果都是一样的:

$$ M = \left( \begin{matrix} 1.0 & 4.0 & 7.0 \\ 2.0 & 5.0 & 8.0 \\ 3.0 & 6.0 & 9.0 \\ \end{matrix} \right) $$

向量与矩阵中的元素是可以单独访问和设置的。向量支持使用分量的名称或这数组的形式访问。矩阵可以以二维数组的形式进行访问。

向量分量的访问符:

分量访问符符号描述
(x, y, z, w)与位置相关的分量
(r, g, b, a)与颜色相关的分量
(s, t, p, q)与纹理坐标相关的分量

通过名称访问向量元素:

float red = color.r;
float v_y = velocity.y;

通过数组访问向量元素:

float red = color[0];
float v_y = velocity[1];

分量访问符的一个常见应用叫做swizzle。例如可以基于输入颜色的红色分量来设置一个亮度值:

vec3 luminance = color.rrr;

类似地改变向量中分量各自的位置:

color = color.abgr;

在一条语句的一个变量中只能使用一种类型的访问符。

矩阵元素的访问可以使用数组标记方式从矩阵中直接得到一个标量值或一组元素:

mat4 m = mat4(2.0f);
vec4 zVec = m[2];
float yScale = m[1][1]; // or m[1].y

结构体可以将不同类型的数据组合到一个结构体当中以简化多组数据传入函数的过程。

定义一个结构体它会自动创建一个新类型并且隐式定义一个构造函数将各种类型的结构体元素作为输入参数

struct Particle {
    float lifttime;
    vec3 position;
    vec3 velocity;
};

Particle p = Particle(10.0f, pos, vel);

与C语言类似若需要获取某个元素可以直接使用.符号。

GLSL支持任意类型的数组(包括结构体数组)。数组的索引可以通过符号[]完成。一个大小为n的数组其索引范围为$[1,n-1]$,负数形式或超出范围的索引不被允许。

数组可以定义为有大小的或者没有大小的。可以使用没有大小的数组作为一个数组变量的前置声明然后重新用一个合适的大小来声明它。声明数组:

// 3个float元素的数组
float coeff[3];
float[3] coeff;
// 未定义维数
int indices[];

数组有构造函数并且可以作为函数的参数和返回类型。静态初始化一个数组的值:

float coeff[3] = float[3](2.38f, 3.14f, 42.0f);

构造函数的维数值可以不填。

GLSL的数组有取长度的方法length()。操作一个数组所有的值:

for (int i = 0; i < coeff.length(); ++i) {
    coeff[i] *= 2.0f;
}

向量和矩阵类型也可以使用length()方法。向量的长度就是它包含的分量的个数,矩阵的长度是它包含列的个数。

mat3x4 m;
int c = m.length(); // 3
int r = m[0].length(); // 4

length()方法会返回一个编译时常量:

mat4 m;
float diagonal[m.length()]; // 数组大小与矩阵大小相等
float x[gl_in.length()];  // 数组大小与几何着色器的输入顶点数相等

多维数组相当于从数组中再创建数组:

float coeff[3][5]; // 大小为3的数组,每个元素包含一个大小为5的数组
coeff[2][1] *= 2.0f;
coeff.length(); // 3
coeff[2]; // 返回一个大小为5的一维数组
coeff[2].length(); // 5

数据类型也可以通过一些修饰符来改变自己的行为:

类型修饰符描述
const将一个变量标记为只读形式。如果它初始化时用的是一个编译时常量那么它本身也会成为编译时常量。
in设置这个变量为着色器阶段的输入变量。
out设置这个变量为着色器阶段的输出变量。
uniform设置这个变量为用户应用程序传递给着色器的数据,它对于给定的图元而言是一个常量。
buffer设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存使用。
shared设置变量是本地工作组中共享的。它只能用于计算着色器中。

每个着色器使用inout关键字设定输入和输出,只要一个输出变量与下一个着色器阶段得输入匹配它就会传递下去。

顶点着色器应该接收的是一种特殊形式的输入,否则会效率低下。顶点着色器的输入特殊在它从顶点数据中直接接收输入。为了定义顶点数据该如何管理使用location这一元数据指定输入变量。顶点着色器需要为它的输入提供一个额外的layout标识,这样才能把它链接到顶点数据。

也可以忽略layout (location = 0)标识符,通过在OpenGL代码中使用glGetAttribLocation()查询属性位置值,但在着色器中设置更节省工作量。

片段着色器需要生成一个最终输出的颜色所以需要一个vec4颜色输出变量,如果片段着色器没有定义输出颜色OpenGL会把物体渲染为黑色或白色。

从一个着色器向另一个着色器发送数据必须在发送方着色器中声明一个输出并在接收方着色器中声明一个类似的输入,当类型和名字都一样的时候OpenGL会把两个变量链接到一起以发送数据。

修改顶点着色器和片段着色器让顶点着色器为片段着色器决定颜色:

#version 460 core
layout (location = 0) in vec4 pos;

out vec4 vertexColor;

void main() {
    gl_Position = pos;
    vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);
};
#version 460 core
layout (location = 0) out vec4 FragColor;

in vec4 vertexColor;

void main() {
    FragColor = vertexColor;
};

在顶点着色器中声明一个vertexColor变量作为vec4输出并在片段着色器中声明了一个类似的vertexColor,由于它们名字相同且类型相同,片段着色器中的vertexColor就和顶点着色器中的vertexColor连接了。顶点着色器中将颜色设置为深红色,最终的效果也是深红色。

完整代码:

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

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "out vec4 vertexColor;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "    vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "in vec4 vertexColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vertexColor;\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    float vertices[] = {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f,
        0.0f, 0.5f, 0.0f
    };
    unsigned int vbo, vao;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

顶点着色器决定颜色

在着色器运行之前uniform修饰符可以指定一个在应用程序中设置好的变量,它不会在图元处理的过程中发生变化。uniform变量在所有可用的着色阶段之间都是共享的,它必须定义为全局变量。任何类型的变量(包括结构体和数组)都可以设置为uniform变量。着色器无法写入到uniform变量,也无法改变它的值。

在片段着色器中声明一个uniform vec4ourColor,并把片段着色器的输出颜色设置为uniform值的内容。

如果声明了一个uniform却在GLSL代码中没用过,编译器会默默移除这个变量导致最后编译出的版本中并不会包含它,这可能会导致几个非常麻烦的错误。

目前这个uniform的值为空,首先找到着色器中uniform属性的索引/位置值,之后可以更新它的值。

使像素随时间的改变而改变颜色中的绿色分量值:

glUniform4f(glGetUniformLocation(shader_program, "ourColor"), 0.0f, std::sin(glfwGetTime()), 0.0f, 1.0f);

通过获取GLFW运行时间后使用正弦/余弦函数让颜色在$[0,1]$之间改变,接着查询uniform变量ourColor的位置值,最后设置uniform值。

查询uniform地址不要求之前使用过着色器程序但是更新一个uniform之前必须先使用程序,因为它是在当前激活的着色器程序中设置uniform的。

在渲染循环中更新uniform并进行绘制。

完整代码:

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

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "out vec4 vertexColor;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "    vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "uniform vec4 ourColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = ourColor;\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    float vertices[] = {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f,
        0.0f, 0.5f, 0.0f
    };
    unsigned int vbo, vao;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program);
        glBindVertexArray(vao);
        glUniform4f(glGetUniformLocation(shader_program, "ourColor"), 0.0f, std::sin(glfwGetTime()), 0.0f, 1.0f);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

在顶点数据只有位置数据的基础上添加颜色数据:

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

顶点着色器:

#version 460 core
layout (location = 0) in vec4 pos;
layout (location = 1) in vec4 color;

out vec4 ourColor;

void main() {
    gl_Position = pos;
    ourColor = color;
}

layout标识符把输入的颜色数据变量位置值设置为1并输出此颜色值。

片段着色器:

#version 460 core
in vec4 ourColor;
out vec4 FragColor;

void main() {
    FragColor = ourColor;
}

输出输入的颜色数据。

重新配置顶点属性指针:

VBO位置、颜色配置

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

完整代码:

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

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "layout (location = 1) in vec4 color;\n"
    "\n"
    "out vec4 ourColor;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "    ourColor = color;\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "in vec4 ourColor;\n"
    "out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = ourColor;\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    float vertices[] = {
        0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
       -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
    };
    unsigned int vbo, vao;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

编写、编译、管理着色器很麻烦,所以可以把它们封装到一个抽象对象中。

shader.h:

#pragma once

#include <glad/glad.h>

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

class Shader {
public:
    unsigned int id;

    Shader(const GLchar* vertex_path, const GLchar* fragment_path) {
        std::string vertex_source, fragment_source;
        std::ifstream vertex_shader_file, fragment_shader_file;
        vertex_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fragment_shader_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try {
            vertex_shader_file.open(vertex_path);
            fragment_shader_file.open(fragment_path);
            std::stringstream vertex_shader_stream, fragment_shader_stream;
            vertex_shader_stream << vertex_shader_file.rdbuf();
            fragment_shader_stream << fragment_shader_file.rdbuf();
            vertex_shader_file.close();
            fragment_shader_file.close();
            vertex_source = vertex_shader_stream.str();
            fragment_source = fragment_shader_stream.str();
        }
        catch (std::ifstream::failure e) {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const char* vertex_shader_source = vertex_source.c_str();
        const char* fragment_shader_source = fragment_source.c_str();
        unsigned int vertex_shader, fragment_shader;
        vertex_shader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
        glCompileShader(vertex_shader);
        fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
        glCompileShader(fragment_shader);
        this->id = glCreateProgram();
        glAttachShader(this->id, vertex_shader);
        glAttachShader(this->id, fragment_shader);
        glLinkProgram(this->id);
        glDeleteShader(vertex_shader);
        glDeleteShader(fragment_shader);
    }

    void Use() {
        glUseProgram(this->id);
    }

    void SetBool(const std::string& name, bool value) const {
        glUniform1i(glGetUniformLocation(this->id, name.c_str()), (int)value);
    }

    void SetInt(const std::string& name, int value) const {
        glUniform1i(glGetUniformLocation(this->id, name.c_str()), value);
    }

    void SetFloat(const std::string& name, float value) const {
        glUniform1f(glGetUniformLocation(this->id, name.c_str()), value);
    }
};

着色器类储存了着色器程序的ID,构造函数需要顶点着色器和片段着色器两个源代码文件路径的参数,另外Use()用来激活着色器程序,所以setter函数能够查询一个uniform的位置值并设置它的值。

使用着色器程序非常简单,只需要创建一个着色器对象并向其构造函数传入顶点着色器和片段着色器的未见路径,在使用时调用Use()即可。

shader.vs(vs后缀代表Vertex Shader):

#version 460 core
layout (location = 0) in vec4 pos;
layout (location = 1) in vec4 color;

out vec4 ourColor;

void main() {
    gl_Position = pos;
    ourColor = color;
}

shader.fs(fs后缀代表Fragment Shader):

#version 460 core
in vec4 ourColor;
out vec4 FragColor;

void main() {
    FragColor = ourColor;
}

完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    float vertices[] = {
        0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
       -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
    };
    unsigned int vbo, vao;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    Shader shader("shader.vs", "shader.fs");
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(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();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

Last modification:February 27th, 2020 at 10:15 am
如果觉得我的文章对你有用,请随意赞赏