OpenGL 三角形
渲染管线
这是常见的渲染管线的图,每一步都以上一步的输出为输入,图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。
图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。
顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理。
图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。
图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的
像素
,生成供片段着色器(Fragment Shader)使用的片段(Fragment)
。在片段着色器运行之前会执行裁切(Clipping)
。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。
片段着色器的主要目的是
计算一个像素的最终颜色
,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做
Alpha测试和混合(Blending)阶段
。这个阶段检测片段的对应的深度(和模板(Stencil))值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)
并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
看代码之前
看代码之前我们要明确怎么写
- 我们要初始化我们使用的工具, 比如:glfw
- 创建一个窗口,并为窗口注册事件监听,后面的绘制都在这个窗口上操作
- 编译着色器
- 编译顶点着色器
- 编译片段着色器
- 生成 program,链接着色器
- 绑定 VAO,VBO,或者以后还可能有 EBO,并设置他们的参数
- 循环中绘制图形
代码
hello_triangle.h
👇
1 |
|
hello_triangle.cpp
👇
1 |
|
代码讲解
对比上次的代码,这次多了不少,除了一些代码结构的改动,剩下的主要是在于
1. 编译着色器
第 29 到 59 行
这里是编译着色器
1 | // ********************* 编译着色器 **************************** |
这里定义了两个着色器源代码
1.1 顶点着色器源码 GLSL
layout (location = 0) in vec3 aPos;
这里是in
代表输入,类型是 vec3 ,含有 3 个值的向量,同时设置了location=0
的位置值
gl_position
为输出,是一种图元类型,类型为 vec4 ,四个分量分别是 gl_position.x , gl_position.y , gl_position.z , gl_position.w ,xyz 是坐标量,w 是到相机的距离(这个以后再研究)
vec 最多有 4 个分量,通过后缀可以看出来
1.2 片段着色器源码 GLSL
out vec4 FragColor
out
代表输出,FragColor
类型是 vec4,四个分量分别是 red , green , blue , alpha(透明度)
每个值的范围都是 0-1,float
1.3 编译操作
我们先调用glCreateShader(GL_VERTEX_SHADER)
创建一个GL_VERTEX_SHADER
类型的着色器,并返回句柄
在调用glShaderSource
来指定着色器源代码
1 | void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length); |
这个函数帮我们把多个字符串拼接成一整个源代码,以便编译
这个函数有四个参数,第一个是我们上一行创建的
shader
的句柄,第二个是字符串的数量(因为有可能我们会编译多个shader
,如果这些shader
的某些源代码是一样的,我们就可以将这部分写成共用的,减少字符串冗余),第三个是字符串数组的起始指针(也就是数组的起始地址,如果只有一个字符串,只需传入这个字符串的首地址即可),最后一个是结束编译源代码的结束标志(这里也是一个指针类型,要求传入一个长度为第二个参数count
的整形数组,为了给每个字符串设置长度限制,如果整形数组里有负数,对应字符串以空字符为结束标志,如果这里直接传入NULL
那么所有字符串都以空字符为结束标志)
glCompileShader
就是编译源代码的过程,我们可以通过glGetShaderiv
来检查是否编译成功
ps : 片段着色器创建的时候,和顶点着色器填写的类型不同,为
GL_FRAGMENT_SHADER
2. 生成 program,链接着色器
第 61 到 72 行
1 | unsigned int shaderProgram = glCreateProgram(); |
这里我们需要将编译好的着色器链接到一个可执行的程序,以便后续一键执行
这里和上面是一样的,我们需要调用一个create
函数,来创建一个program
,并拿到他的句柄
glAttachShader
是用来着色器附到program
上面
glLinkProgram
是用来将他们链接起来,这步操作会生成着色器对应的可执行文件
同样我们可以使用glGetProgramiv
来检验是否链接成功
然后,我们链接成功了,编译好的Shader
就没用了,可以直接删除了
直接调用glDelete
将它删除
3. VAO 和 VBO
顶点数组对象:
Vertex Array Object,VAO
VAO
存储顶点属性(vertex attributes)的调用,这个有点抽象,我们后面详细讲解。顶点缓冲对象:
Vertex Buffer Object,VBO
VBO
存储顶点本身,是为了一次性向GPU
传输大量顶点,因为从CPU
往GPU
发送数据是相对慢的。
第 74 到 102 行
1 | // ******************************************************************************** |
这里定义了 vertices
也就是顶点,为了说明三角形的位置
1 | float vertices[] = { |
这里是 3D 坐标,所以最后一个都是 0
图形大概长这样
3.1 VBO 和 顶点缓冲对象
1 | // 绑定 VBO |
glBind
来绑定 VAO
和 VBO
glBufferData
会把之前定义的顶点数据复制到缓冲的内存中,所以这个函数只能在绑定了 VBO 之后调用
1 | /** |
3.2 VAO 和 顶点属性
1 | // 绑定 VAO |
我们之前说的了—— VAO
存储顶点属性(vertex attributes)的调用,我们使用 learn-opengl 的图来解释一下
可以看到 VAO
里面都是 pointer
,我们将一开始写的那个 float
数组存放在 VBO
里面,然后调用 glVertexAttribPointer
进行设置属性调用指针(从函数名里的 pointer
就能看出来)
接下来我们来看这个 glVertexAttribPointer
函数:
1 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) nullptr); |
1 | /** |
这样我们的顶点属性就设置好了,最后调用一句glEnableVertexAttribArray(0);
让这个location
为0
的定点生效(激活)
每个顶点属性从一个
VBO
管理的内存中获得它的数据,而具体是从哪个VBO
(程序中可以有多个VBO
)获取则是通过在调用
glVertexAttribPointer
时绑定到GL_ARRAY_BUFFER
的VBO决定的。由于在调用
glVertexAttribPointer
之前绑定的是先前定义的VBO
对象,顶点属性0
现在会链接到它的顶点数据。
3.3 解绑
1 | glBindBuffer(GL_ARRAY_BUFFER, 0); |
然后我们就可以解绑 VAO
和 VBO
了,可以看到,我们希望 VAO
和 VBO
对应起来,就需要绑定并设置他们,然后再解绑
在后面我们需要调用 VAO 来绘制的时候,也是这样
绑定 -> 操作 -> 解绑
只不过这是只有一个VAO
,没有切换到其他VAO
的需求,所以我们没必要不断绑定再解绑,但如果写了解绑其实更加标准,更加完整。
4. 开始绘制
1 | while (!glfwWindowShouldClose(window)) { |
这里我们用一个循环不断执行绘制流程,这个上篇文章就讲过,我们这里尤其讲一下这个draw_frame
1 | void hello_triangle::draw_frame(unsigned int shaderProgram, unsigned int VAO) { |
我们这里中看下最后四行代码
1 | // draw our first triangle |
可以看到在调用了glUseProgram
之后,和之前说的一样,绑定 -> 绘制 -> 解绑
1 | /** |
到这里,三角形应该就会出现在你的屏幕上了
开始欢呼吧!!!🎉🎉🎉
- Title: OpenGL 三角形
- Author: lucas
- Created at : 2023-08-15 22:31:41
- Updated at : 2024-11-27 17:10:43
- Link: https://darkflamemasterdev.github.io/2023/08/15/OpenGL-三角形/
- License: This work is licensed under CC BY-NC-SA 4.0.