OpenGL-顶点着色器

顶点

顶点用于图元装配,OpenGL支持的图元有三种:点、线、三角形,对于点只需要一个顶点,对于三角形需要三个顶点。

1
2
3
4
5
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};

顶点坐标

OpenGL不是简单的把所有的3D坐标变换成屏幕上的2D像素,仅当3D坐标在3个轴上-1.0到1.0的范围内时才处理它,所有在这个范围内的坐标叫做标准化

NDC
(0,0)是坐标的中心,y轴正方向向上,x轴正方向向右。

VBO(Vertex Buffer Objects)

将顶点数据作为输入发送给顶点着色器后,它会在GPU上创建一块内存用于存储顶点数据。使用顶点缓冲对象可以用来管理这块内存,用缓冲对象可以一次性的发送大量数据到GPU上,CPU到GPU的传输比较慢,所以尽量一次性发送尽可能多的数据。

使用glGenBuffers函数生成一个缓冲对象

1
2
unsigned int VBO;
glGenBuffers(1, &VBO);

OpenGL有很多缓冲对象类型,顶点缓冲对象的类型是GL_ARRAY_BUFFER,OpenGL允许同时绑定多个缓冲,但是缓存类型不能相同,使用glBindBuffer将创建的缓存对象绑定到GL_ARRAY_BUFFER上。

1
glBindBuffer(GL_ARRAY_BUFFER, VBO);  

绑定之后,任何针对GL_ARRAY_BUFFER的缓存配置都会作用到绑定的缓冲对象上,使用glBufferData函数可以将顶点数据复制到缓对象管理的内存中。如果缓冲中的数据会频繁变化,需要使用GL_DYNAMIC_DRAW,这样GPU会把数据放在能够高速写入的内存部分。

1
2
3
4
// GL_STATIC_DRAW: 数据不会或几乎不会改变
// GL_DYNAMIC_DRAW: 数据会被改变很多
// GL_STREAM_DRAW: 数据每次绘制都会改变
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

链接顶点属性

通过VBO上传了顶点数据,编写了顶点着色器后,需要将顶点数据链接到着色器中的in属性上,通过glVertexAttribPointer将当前绑定到GL_ARRAY_BUFFER类型上的VBO中的数据链接到对应位置的顶点in属性上。

1
2
3
4
5
6
7
8
// 0: in属性location
// 3: vec3
// 浮点型
// 将顶点数据标准化
// 步长,两个顶点数据的间隔字节数量
// 数据偏移
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);// 默认是禁用

VAO(Vertex Array Object)

如果有多个顶点属性需要配置,可以使用顶点数组对象,顶点数组对象可以像顶点缓冲对象那边被绑定,可以将多个顶点属性的配置都绑定到VAO上,这样只需要配置一次,切glVertexAttribPointer换不同的属性配置时只需要绑定不同的VAO即可。

创建VAO

1
2
unsigned int VAO;
glGenVertexArrays(1, &VAO);

绑定VAO,当绑定VAO后,后面调用glVertexAttribPointer对顶点属性进行的配置都会自动存储到VAO中。

1
glBindVertexArray(VAO);

EBO(Element Buffer Object)

元素缓冲对象也叫索引缓冲对象。EBO是一个缓冲区,和VBO一样,存储OpenGL用来决定要绘制哪些顶点的索引,索引定义了顶点的组合方式,顶点可以重复使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};

unsigned int indices[] = {
// 注意索引从0开始!
// 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
// 这样可以由下标代表顶点组合成矩形

0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};

创建EBO

1
2
unsigned int EBO;
glGenBuffers(1, &EBO);

EBO的类型是GL_ELEMENT_ARRAY_BUFFER,使用glBindBuffer将创建的缓存对象绑定到GL_ELEMENT_ARRAY_BUFFER上。

1
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);  

绑定之后,任何针对GL_ELEMENT_ARRAY_BUFFER的缓存配置都会作用到绑定的缓冲对象上,使用glBufferData函数可以将索引数据复制到缓对象管理的内存中。如果缓冲中的数据会频繁变化,需要使用GL_DYNAMIC_DRAW,这样GPU会把数据放在能够高速写入的内存部分。

1
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

使用EBO后,需要用glDrawElements替换glDrawArrays。

1
2
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

EBO也可以绑定到VAO中,只需绑定一次,后续只绑定VAO即可。

顶点着色器(Vertex Shader)

图形渲染管线的第一部分是顶点着色器,着色器可以看成是一个纯函数,输入的所有顶点,都会依次执行该函数,在该函数中对输入的顶点进行处理,然后输出新的顶点位置。

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

out vec2 TexCoord;

uniform mat4 transform;

void main()
{
gl_Position = transform * vec4(aPos, 1.0);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

顶点坐标

顶点着色器中通过定义in变量来接收外部定义的顶点数据,通过glVertexAttribPointer将VBO数据绑定到aPos变量上,这个输入的顶点都会依次执行该着色器函数,通过定义一个uniform的矩阵变量可以实时对顶点坐标进行变换。gl_Position输出最终变换过的顶点坐标。

纹理坐标

通过顶点着色器传递纹理坐标给片段着色器,可在光栅化时对坐标进行片段插值。