图形渲染管线的每个阶段的抽象展示
蓝色部分代表的是我们可以注入自定义的着色器的部分。现代OpenGL中,我们必须定义至少一个顶点着色器
和一个片段着色器
(因为GPU中没有默认的顶点/片段着色器)。
顶点输入
OpenGL是一个3D图形库,要先给OpenGL输入一些顶点数据。OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0
到1.0
的范围内时才处理它。当将它顶点的z坐标设置为0.0
,这样子的话三角形每一点的深度(Depth)
都是一样的,从而使它看上去像是2D的。通常深度可以理解为z坐标,它代表一个像素在空间中和你的距离,如果离你远就可能被别的像素遮挡,你就看不到它了,它会被丢弃,以节省资源。
OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理GPU内存尽量一次性发送尽可能多的数据,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers
函数和一个缓冲ID生成一个VBO对象,使用glBindBuffer
函数把新创建的缓冲绑定到GL_ARRAY_BUFFER
目标上:
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER
目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后,定义一个float数组的三角形顶点:
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
调用glBufferData函数,它会把定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData
是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER
目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof
计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
GL_STATIC_DRAW
:数据不会或几乎不会改变。
GL_DYNAMIC_DRAW
:数据会被改变很多。
GL_STREAM_DRAW
:数据每次绘制时都会改变。
三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW
。
顶点着色器
首先是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器:
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
在GLSL中一个向量有最多4个分量,可以通过vec.x
、vec.y
、vec.z
和vec.w
来获取。注意vec.w
分量不是用作表达空间中的位置的(我们处理的是3D不是4D),而是用在所谓透视除法(Perspective Division)上。
这里对输入数据什么都没有处理就把它传到着色器的输出了。但在真实的程序里输入数据通常都不是标准化设备坐标,需要首先必须先把它们转换至OpenGL的可视区域内。
片段着色器
片段着色器所做的是计算像素最后的颜色输出。
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,我们应该自己将其计算出来。声明输出变量可以使用out关键字,这里我们命名为FragColor。下面,我们将一个Alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。
两个着色器现在都编译了,剩下的事情是把两个着色器对象链接到一个用来渲染的着色器程序(Shader Program)中。
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
链接主要接口:glCreateProgram
,glAttachShader
,glLinkProgram
激活程序对象:glUseProgram
现在,已经把输入顶点数据发送给了GPU,并指示了GPU如何在顶点和片段着色器中处理它。但OpenGL还不知道它该如何解释内存中的顶点数据,以及它该如何将顶点数据链接到顶点着色器的属性上。我们需要告诉OpenGL怎么做。
链接顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer
函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上),函数的参数非常多:
第一个参数指定我们要配置的顶点属性。
第二个参数指定顶点属性的大小。这里顶点属性是一个vec3。
第三个参数指定数据的类型,这里是GL_FLOAT(按vertices类型)。
第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。
第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。
最后一个参数表示位置数据在缓冲中起始位置的偏移量(Offset)。