OpenGL 着色器程序
一些信息
- 着色器程序对象(Shader Program Object) 是多个着色器程序合并之后并最终链接完成的版本
- 在渲染对象的时候会激活这个着色器程序
- 已激活的着色器程序将在发送渲染调用的时候被使用
- 当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入
链接的过程
创建一个程序对象
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
将着色器附加到程序上
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
检查链接是否成功
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
...
}
激活程序对象
glUseProgram(shaderProgram);
在glUseProgram
函数调用后,每个着色器调用和渲染调用都会使用这个程序对象
把着色器对象链接到程序对象后,删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
到此处,我们已经将输入的顶点数据发送给了 GPU, 并指示了 GPU 如何在顶点和片段着色器中处理它
但是 OpenGL 还不知道如何解释内存中的顶点数据,如何将顶点数据链接到顶点着色器的属性上
链接顶点属性
顶点缓冲数据的解析(我们所使用的例子)
vertex1 | vertex1 | vertex1 | ||||||
x | y | z | x | y | z | x | y | z |
\(-OFFSET:0\)
- 位置数据被存储为 32 位(4字节)浮点值
- 每个位置包含 3 个这样的值
- 在这3个值之间没有空隙(或其他值) 这几个值在数组中 紧密排列 (Tightly Packed)
- 数据中第一个值在缓冲开始的位置
告诉OpenGL如何解析顶点数据
用 glVertexAttribPointer
来告诉 OpenGL 如何解析
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // 启用顶点属性 参数是顶点属性位置
glVertexAttribPointer 参数解析
- 第一个参数为顶点着色器中
layout (location=0) in vec3 position;
中的location的值 - 第二个参数为指定顶点属性的维数
- 第三个参数指定数据的类型
- 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
- 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个
float
之后,我们把步长设置为3 * sizeof(float)
。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。 - 最后一个参数的类型是
void*
,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
每个顶点属性从一个 VBO(由glVertexAttribPointer
时绑定到GL_ARRAY_BUFFER
的VBO
决定) 管理的内存中获取数据
一个在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();
每绘制一个物体都必须重复此过程,但是为每个物体配置所有顶点属性很麻烦
解决办法是把所有状态配置存储在一个对象中,通过绑定此对象来恢复状态
欲知具体如何还请接着往下看
顶点数据对象
VAO 不是 缓冲对象,因此不存储数据,VAO存储一次绘制所需的全部信息(配置以及VBO的使用方法),它包含一下信息
glEnableVertexAttribArray
和glDisableVertexAttribArray
的调用。- 通过
glVertexAttribPointer
设置的顶点属性配置。 - 通过
glVertexAttribPointer
调用与顶点属性关联的顶点缓冲对象。
VAO的创建
unsigned int VAO;
glGenVertexArrays(1, &VAO);
而使用 VAO 只需,绑定即可。
在绑定之后,再绑定和配置对应的 VBO 和属性指针,之后解绑 VAO 供以后使用
在绘制物体的时候,只需要把 VAO 绑定到要使用的设定上即可
其代码应该长这样
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();