顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。
我们的顶点缓冲数据会被解析为下面这样子:
1.位置数据被储存为32位(4字节)浮点值。
2.每个位置包含3个这样的值。
3.在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
4.数据中第一个值在缓冲开始的位置。
有了这些信息我们就可以使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)了:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); // 设置顶点属性指针
// 0: 顶点属性位置值 3: 顶点属性大小 GL_FLOAT: 顶点属性类型 GL_FALSE: 是否标准化 3 * sizeof(float): 步长 (void*)0: 偏移量
glEnableVertexAttribArray(0); // 启用顶点属性 0: 顶点属性位置值 glEnableVertexAttribArray: 启用顶点属性
glVertexAttribPointer函数的参数非常多,所以我会逐一介绍它们:
第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec都是由浮点数值组成的)。
第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。由于在调用glVertexAttribPointer之前绑定的是先前定义的VBO对象,顶点属性0现在会链接到它的顶点数据。
现在我们已经定义了OpenGL该如何解释顶点数据,我们现在应该使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。自此,所有东西都已经设置好了:我们使用一个顶点缓冲对象将顶点数据初始化至缓冲中,建立了一个顶点和一个片段着色器,并告诉了OpenGL如何把顶点数据链接到顶点着色器的顶点属性上。在OpenGL中绘制一个物体,代码会像是这样:
// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定缓冲 GL_ARRAY_BUFFER: 缓冲类型 VBO: 缓冲ID glBindBuffer: 绑定缓冲
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 复制顶点数组到缓冲中供OpenGL使用 GL_ARRAY_BUFFER: 缓冲类型 sizeof(vertices): 数据大小 vertices: 数据 GL_STATIC_DRAW: 数据使用方式
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); // 设置顶点属性指针 0: 顶点属性位置值 3: 顶点属性大小 GL_FLOAT: 顶点属性类型 GL_FALSE: 是否标准化 3 * sizeof(float): 步长 (void*)0: 偏移量
glEnableVertexAttribArray(0); // 启用顶点属性 0: 顶点属性位置值 glEnableVertexAttribArray: 启用顶点属性
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram); // 使用着色器程序 shaderProgram: 着色器程序 glUseProgram: 使用着色器程序
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle(); // 绘制物体 someOpenGLFunctionThatDrawsOurTriangle: 绘制物体