高级数据
在之前的章节中,我们广泛的使用了OpenGL中的缓冲来在GPU上存储数据,这一章来讨论一下管理缓冲的其它方法。
缓冲在OpenGL中,是一个管理一片GPU内存空间的对象,我们将其绑定到特定的缓冲区来赋予缓冲对象意义。
到目前为止,我们使用glBufferData来填充缓冲的内存空间,这个方法会分配一片GPU内存,并将数据存储到这片内存。如果我们传入NULL,则只会分配内存空间。
其实我们可以只填充缓冲的特定区域,使用glBufferSubData。这个方法要求一个缓冲区参数,一个偏移参数,一个数据大小的参数,以及要传入的数据。我们使用这个偏移参数来指定要填充数据的位置。注意,为了确保缓冲有足够的内存空间,最好在调用glBufferSubData前调用glBufferData,下面是glBufferSubData的一个例子:
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // 范围: [24, 24 + sizeof(data)]
当然,填充数据还有另一种方法,我们可以创建指向一块缓冲内存的指针,然后将数据复制到这块区域。我们可以使用glMapBuffer创建一块缓冲内存区域并返回一个指针。下前面是一个例子:
float data[] = {
0.5f, 1.0f, -0.35f
...
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 创建指针
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 将数据复制到内存
memcpy(ptr, data, sizeof(data));
// 我们不再需要这个指针
glUnmapBuffer(GL_ARRAY_BUFFER);
我们使用glUnmapBuffer来结束指针的操作。使用glMapBuffer的优势是不需要使用中转内存区域。
批处理顶点属性
使用glVertexAttribPointer我们可以对VAO中的顶点属性进行位置分配,但之前我们都是使用glBufferData一次传入数据,所以是按照位置、法线、纹理坐标的顺序来紧密排列每个顶点,我们可以使用glBufferSubData来自定义顺序。
我们可以单独定义顶点的位置,法线和纹理坐标,并按照顺序传入缓冲区的内存中,内存中存储的顺序为:所有顶点的位置--所有顶点的法线--所有顶点的纹理坐标。例子如下:
float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// fill buffer
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
这样,我们也需要修改glVertexAttribPointer的配置:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));
glVertexAttribPointer(
2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
复制缓冲
如果你的缓冲对象中填满了数据,我们可以将数据复制到另一个缓冲对象。我们使用glCopyBufferSubData来完成这一操作:
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,
GLintptr writeoffset, GLsizeiptr size);
readtarget和witetarget分别代表贡献数据的缓冲区和获得数据的缓冲区,readoffset和writeoffset代表两个缓冲区执行操作开始的位置,size代表复制数据的大小。例如,我们可以将VERTEX_ARRAY_BUFFER上的数据复制到VERTEX_ELEMENT_ARRAY_BUFFER上。但如果想要在两个同一缓冲区上的缓冲对象之间进行复制操作呢?基于这一问题,OpenGL提供了两个缓冲区:GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER,我们只需要在进行复制操作前将两个缓冲对象绑定至这两个缓冲区就行:
glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 8 * sizeof(float));
我们也可以只将待写入的缓冲对象绑定至GL_COPY_WRITE_BUFFER:
float vertexData[] = { ... };
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 8 * sizeof(float));