OpenGL入门002——顶点着色器和片段着色器

一些概念

坐标转换阶段

概述: 模型空间、世界空间、视图空间和裁剪空间是对象在3D场景中经历的不同坐标变换阶段。每个空间对应渲染管道的一个步骤,逐步将模型从其初始位置转换到最终屏幕上的位置

模型空间:

  • 定义:这是对象的本地坐标系,是模型创建时的坐标
  • 作用:模型空间定义了物体的基本形状和几何信息,不受场景中其他物体位置的影响。每个模型都有自己的模型空间坐标
  • 转换:通过模型矩阵,可以将模型空间的坐标转换为世界空间

世界空间:

  • 定义:这是整个场景的坐标系,所有模型在世界空间中都有唯一的位置和方向,可以视为“全局坐标系”
  • 作用:将所有对象放置在同一个坐标系统中,确保它们相对位置正确
  • 转换:通过应用视图矩阵,将世界空间的坐标转换为视图空间,视图矩阵通常基于摄像机的位置和方向

视图空间:

  • 定义:又称为“摄像机空间“,是以摄像机位置为原点的坐标系,此时场景中的所有对象都以摄像机为参考重新定位
  • 作用:使得所有对象相对于摄像机的位置和方向变得更直观,便于确定哪些对象可见、如何投影到屏幕
  • 转换:使用投影矩阵将视图空间转换为裁剪空间,这一步决定了图像的投影类型(如透视投影或正交投影)

裁剪空间:

  • 定义:应用投影变换后的空间,此时3D场景的坐标被转换为一个标准化的3D盒子,所有可见的坐标x,y,z值均被限制在-1到1之间
  • 作用:裁剪空间便于对视锥外部的物体进行裁剪,只保留可见部分。裁剪后的坐标将进行透视除法,映射到2D屏幕上的坐标,即归一化设备坐标
  • 转换:裁剪空间进一步转换为屏幕空间,经过视口变换,使坐标适配屏幕的分辨率和比例

顶点着色器

概述: 顶点着色器(Vertex Shader)是对输入的顶点进行处理,顶点是组成几何体的基本元素,比如三角形的每个角都是一个顶点

作用:

  • 顶点位置变换:顶点着色器通常会将顶点从模型空间(即对象的局部坐标系)转换到世界空间、视图空间,最后转换到裁剪空间,以便在屏幕上正确显示
  • 顶点属性处理:除了位置,顶点着色器还可以处理其他与顶点相关的属性,比如法线、纹理坐标、颜色等
  • 光照计算:在某些情况下,顶点着色器可以进行基础的光照计算,如使用法线来计算顶点的光照效果

输入: 顶点的坐标、法线、纹理坐标等数据

输出: 处理后的顶点坐标,如变换后的顶点位置和其他顶点属性,供后续的图形流水线使用

片段着色器

概述: 片段着色器(Framgment Shader)是屏幕上每个像素的潜在颜色值,在光栅化阶段之后,每个几何体被转换为一系列像素片段,片段着色器负责确定这些片段的最终颜色

作用:

  • 颜色计算:片段着色器通过对纹理、光照、材质等信息的处理,确定每个像素的颜色,它通常会结合插值后的顶点属性(如纹理坐标或颜色)进行复杂的颜色计算
  • 光照效果:片段着色器可以进行精确的光照计算,以便生成更逼真的阴影和高光效果
  • 纹理映射:片段着色器可以从纹理中采样,根据纹理坐标获取纹理颜色,并应用到片段上

输入: 每个片段的插值属性(如纹理坐标、颜色、发现等)

输出: 每个片段的最终颜色值,传递给屏幕或帧缓冲区

VBO

概述: VBO(Vertex Buffer Object)是一个存储在GPU内存中的缓冲区,用于存放顶点数据。通常,顶点数据包含顶点的坐标、颜色、法线、纹理坐标等信息。通过使用VBO,程序可以将这些顶点数据传输到GPU,这样GPU可以在渲染时快速访问它们,而不需要每帧都从CPU传输数据

关键步骤:

  • 创建VBO:使用glGenBuffers()创建一个VBO
  • 绑定VBO:使用glBindBuffer()绑定VBO,指定将要存储的缓冲数据类型(如顶点数据GL_ARRY_BUFFER)
  • 填充VBO:使用glBufferData()将顶点数据传输到VBO中

VAO

概述: VAO(Vertex Array Object)是一个用于保存VBO配置的对象,它记录了与绘制顶点相关的所有状态信息。VAO不仅仅是VBO的一个包装器,它还保存了顶点属性指针和启用状态。例如:每个顶点的布局、数据的解释方式(如步幅、偏移量),以及使用的VBO。使用VAO可以简化渲染过程,因为当VAO被绑定时,所有与其关联的VBO和顶点属性信息都会自动生效。

关键步骤:

  • 创建VAO:使用glGenVertexArrays()创建一个VAO
  • 绑定VAO:使用glBindVertexArray()绑定VAO
  • 设置顶点属性指针:使用glVertexAttribPointer()设置顶点属性指针(告诉OpenGL如何解释顶点数据)
  • 启用顶点属性:使用glEnableVertexAttribArray()启用顶点属性

实战

简介

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

目的: 绘制一个三角形

环境:

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

main.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

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

// 屏幕宽度
const unsigned int SCR_WIDTH = 800;
// 屏幕高度
const unsigned int SCR_HEIGHT = 600;

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

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

// 顶点着色器源码
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";

int main() {
// 初始化glfw
glfwInit();
// 设置opengl版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 使用核心模式:确保不使用任何被弃用的功能
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

// 创建glfw窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "I am window title", NULL, NULL);
if (window == NULL) {
cout << "Failed to create glfw window" << endl;
// 终止GLFW
glfwTerminate();
return -1;
}
// 设置当前窗口的上下文
glfwMakeContextCurrent(window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

// 加载opengl函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
cout << "Failed to initialize GLAD" << endl;
return -1;
}

// 构建并编译顶点着色程序
// 创建一个着色器对象,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;
}
// 创建着色器程序对象
unsigned int 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);

// 设置三角形的顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 左下角
0.5f, -0.5f, 0.0f, // 右下角
0.0f, 0.5f, 0.0f // 顶部
};
// 生成一个VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// 绑定VAO,使其成为当前操作的VAO
glBindVertexArray(VAO);
// 生成一个VBO
unsigned int VBO;
glGenBuffers(1, &VBO);
// 绑定VBO, 使其成为当前操作的VBO,GL_ARRAY_BUFFER表示顶点缓冲区
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 为当前绑定的VBO创建并初始化数据存储,GL_STATIC_DRAW表示数据将一次性提供给缓冲区,并且在之后的绘制过程中不会频繁更改
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 定义顶点属性的布局
// - index:顶点属性的索引
// - size:每个顶点属性的数量,每个顶点有三个分享
// - type:数据类型
// - normalized:是否将非浮点数值归一化
// - stride:连续顶点属性之间的间隔
// - pointer:数据在缓冲区中的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
// 启用顶点属性数组
glEnableVertexAttribArray(0);
// 解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑VAO
glBindVertexArray(0);

// 循环渲染
while (!glfwWindowShouldClose(window)) { // 检查是否应该关闭窗口
// 处理输入
process_input(window);

// 清空屏幕所用的颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// 清空颜色缓冲,主要目的是为每一帧的渲染准备一个干净的画布
glClear(GL_COLOR_BUFFER_BIT);

// 使用着色器程序
glUseProgram(shaderProgram);
// 绑定VAO
glBindVertexArray(VAO);
// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0 ,3);

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

// 删除VAO
glDeleteVertexArrays(1, &VAO);
// 删除VBO
glDeleteBuffers(1, &VBO);
// 删除着色器程序
glDeleteProgram(shaderProgram);

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

CMakeLists.txt

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

# 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)

# 添加可执行文件
add_executable(HelloTriangle main.cpp)

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

最终效果


OpenGL入门002——顶点着色器和片段着色器
http://example.com/2024/11/01/OpenGL入门002——顶点着色器和片段着色器/
作者
凌云行者
发布于
2024年11月1日
许可协议