一、缓冲的填充与修改
首先需要调用glBufferData给缓冲分配足够的内存。
glBufferSubData
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
// example:
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data);
target: 缓冲区对象的类型,取值可以是GL_ARRAY_BUFFER,GL_ELEMENT_ARRAY_BUFFER或者GL_UNIFORM_BUFFER等。
offset: 相对于缓冲区起始位置的偏移量,指定了数据将要传输到缓冲区的哪个位置。
size: 需要传输的数据大小,以字节为单位。
data: 指向包含要传输的数据的指针。
须知:
1. 将数据直接拷贝到GPU的内存中。
2. 适用于数据量小、数据频繁变化或只需要部分数据更新
3. 频繁调用对CPU之间GPU带宽有一定影响
2. glMapBuffer
void* glMapBuffer(GLenum target, GLenum access);
target:要映射的缓冲区目标,可以是以下任一值之一:
GL_ARRAY_BUFFER:用于顶点数据的缓冲区对象。
GL_ELEMENT_ARRAY_BUFFER:用于索引数据的缓冲区对象。
GL_UNIFORM_BUFFER:用于Uniform数据的缓冲区对象。
GL_TRANSFORM_FEEDBACK_BUFFER:用于Transform Feedback数据的缓冲区对象。
GL_SHADER_STORAGE_BUFFER:用于Shader存储数据的缓冲区对象。
access:映射缓冲区的访问方式,可以是以下任一值之一:
GL_READ_ONLY:只读访问。
GL_WRITE_ONLY:只写访问。
GL_READ_WRITE:读写访问。
原理:
1. 调用glMapBuffer后,使用 映射操作,会将GPU上指定缓冲区的内容一模一样拷贝一份至CPU的内存上(也称为 映射区域),并返回 内存指针。之后的可通过指针填充或修改缓冲数据,当然这些修改都只是在CPU的内存之上,并且OpenGL实现会将应用程序所做的修改缓存下来,等到 解除映射(调用glUnmapBuffer)时一次性拷贝到GPU内存。
2. 当缓冲区对象被映射时,OpenGL会将该缓冲区对象的使用限制为GL_READ_WRITE。这样做是为了方便读取和修改相应缓冲区对象的数据。
3. 解除映射之前,其他OpenGL API要是想读取或修改缓冲区的内容,就必须使用 同步机制以保证数据的一致性。因此会在他们进行读取和修改时,OpenGL就会强制提前将 整个映射区域的数据拷贝到GPU内存中,这样就会导致多次数据拷贝,从而降低性能。通常在映射期间,应该尽量避免其他OpenGL API对相应的缓冲区对象进行修改。
例子:
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));
// 记得告诉OpenGL我们不再需要这个指针了
glUnmapBuffer(GL_ARRAY_BUFFER);
二、分批顶点属性
之前的加载顶点数据时,通过glBufferData一次性将所有数据将在到顶点缓冲中。且其中的数据采用了交错(Interleave)处理,即将每一个顶点的位置、法线和/或纹理坐标紧密放置在一起。如123123的形式。
但当从文件中加载顶点数据时,通常获得是一个位置数组、一个法线数组和/或一个纹理坐标数组。通常是11122233的形式。因此我们可以使用glBufferSubData分批将这些属性加载到顶点缓冲。当然前提是需要glBufferData预先分配好足够的内存。
float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// 填充缓冲
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(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)));
三、复制缓冲
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,
GLintptr writeoffset, GLsizeiptr size);
readTarget:指定源缓冲区对象的目标类型,可以是GL_ARRAY_BUFFER、GL_COPY_READ_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER中的任意一个。
writeTarget:指定目标缓冲区对象的目标类型,可以是GL_ARRAY_BUFFER、GL_COPY_WRITE_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_UNPACK_BUFFER中的任意一个。
readOffset:指定源缓冲区对象中的偏移量,以字节为单位。
writeOffset:指定目标缓冲区对象中的偏移量,以字节为单位。
size:指定拷贝数据的字节数。
该函数会将源缓冲区对象中从偏移量readOffset开始的size个字节的数据拷贝到目标缓冲区对象中的偏移量writeOffset处,两个缓冲区对象的数据类型和大小必须一致。
缓冲目标相同时
使用OpenGL提供给我们另外两个缓冲目标,叫做GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER。
float vertexData[] = { ... };
glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
or
float vertexData[] = { ... };
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));