1.顶点输入
开始绘制图形之前,我们必须先给OpenGL输入一些顶点数据。OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它.
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
1.1顶点着色器
它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。
1.2顶点缓冲对象(Vertex Buffer Objects, VBO)
它会在GPU内存(通常被称为显存)中储存大量顶点。
就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象。
unsigned int VBO;
glGenBuffers(1, &VBO);
顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER,使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
2.顶点着色器
用着色器语言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);
}
每个着色器都起始于一个版本声明,使用in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute),我们就创建一个vec3输入变量aPos。为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,可以把vec3的数据作为vec4构造器的参数。
3.编译着色器
暂时将顶点着色器的源代码硬编码在代码文件顶部的C风格字符串中,为了能够让OpenGL使用它,我们必须在运行时动态编译它的源代码
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
3.1.创建一个着色器对象
注意还是用ID来引用的。所以我们储存这个顶点着色器为unsigned int,然后用glCreateShader创建这个着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
3.2.把这个着色器源码附加到着色器对象上,然后编译它:
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL。
4.片段着色器
片段着色器所做的是计算像素最后的颜色输出。片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色。编译片段着色器的过程与顶点着色器类似,只不过我们使用GL_FRAGMENT_SHADER常量作为着色器类型:
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
5.着色器程序
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
5.1.创建一个程序对象
glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。现在我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们:
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。现在我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们:
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
5.2.激活这个程序
创建一个程序对象调用glUseProgram函数,用刚创建的程序对象作为它的参数,以激活这个程序对象,在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象
glUseProgram(shaderProgram);
glDeleteShader(vertexShader);//删除着色器对象
glDeleteShader(fragmentShader);
6.链接顶点属性
使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
在OpenGL中绘制一个物体
// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
7.顶点数组对象(Vertex Array Object, VAO)