本篇在讲什么 关于OpenGL数据缓冲的相关内容 本篇适合什么 适合初学OpenGL的小白 想了解OpenGL缓冲对象的同学 本篇需要什么 对C++语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 |
★提高阅读体验★ 👉 ♠ 一级标题 👈👉 ♥ 二级标题 👈👉 ♣ 三级标题 👈👉 ♦ 四级标题 👈 |
目录
- ♠ 创建缓冲和分配内存
- ♥ 创建缓存
- ♥ 绑定缓存对象
- ♥ 分配内存
- ♥ 更新数据
- ♣ BufferSubData
- ♣ MapBuffer
- ♣ MapBufferRange
- ♠ 填充以及拷贝数据到缓冲
- ♥ 填充常数值
- ♥ 缓存之间复制数据
- ♥ 使用缓冲为顶点着色器提供数据
- ♥ 将缓存绑定给VAO
- ♥ 描述数据的布局和格式
- ♥ 设置顶点属性来引用缓存的绑定
- ♥ 演示代码
- ♠ 整体的演示效果
- ♠ 推送
- ♠ 结语
♠ 创建缓冲和分配内存
♥ 创建缓存
我们通过glGenBuffers
接口来创建一个或多个缓存对象名称
void glGenBuffers(GLsizei n, GLuint *buffers)
参数1:n
指需要创建的缓存对象数量
参数2:buffers
用来存储缓存对象名称
的一个或多个缓存对象
注意:这里创建的并非真正的缓存对象,仅仅是缓存对象的名称
♥ 绑定缓存对象
我们通过glBindBuffer
接口将缓存对象绑定到当前OpenGl环境中
void glBindBuffer(GLenum target, GLuint buffer)
参数1:target
指缓冲区对象绑定到的目标,也可以理解为缓冲对象的类型
参数2:buffer
缓冲区对象的名称
注意:真正的缓冲对象在绑定后才会创建出来,虽然它暂时只是一块没有任何数据的内存区域
- target类型总览
Buffer Binding Target | Purpose |
---|---|
GL_ARRAY_BUFFER | Vertex attributes |
GL_ATOMIC_COUNTER_BUFFER | Atomic counter storage |
GL_COPY_READ_BUFFER | Buffer copy source |
GL_COPY_WRITE_BUFFER | Buffer copy destination |
GL_DISPATCH_INDIRECT_BUFFER | Indirect compute dispatch commands |
GL_DRAW_INDIRECT_BUFFER | Indirect command arguments |
GL_ELEMENT_ARRAY_BUFFER | Vertex array indices |
GL_PIXEL_PACK_BUFFER | Pixel read target |
GL_PIXEL_UNPACK_BUFFER | Texture data source |
GL_SHADER_STORAGE_BUFFER | Read-write storage for shaders |
GL_TEXTURE_BUFFER | Texture data buffer |
GL_TRANSFORM_FEEDBACK_BUFFER | Transform feedback buffer |
GL_UNIFORM_BUFFER | Uniform block storage |
传送门:参考页面
♥ 分配内存
在使用缓存对象之前我们需要为缓存对象分配内存空间
void glBufferStorage(GLenum target, GLSizeiptr size, const void* data, GLbitfield flags)
void glNamedBufferStorage(GLuint buffer, GLSizeiptr size, const void* data, GLbitfield flags)
参数1:target
缓冲对象的类型, buffer
缓存对象
参数2:size
内存大小
参数3:data
想要初始化缓存的数据,一个指针,设null则没有初始化
参数4:flags
指示OpenGL如何使用缓存对象
glBufferStorage
接口作用于某个类型的缓存对象,glNamedBufferStorage
直接作用于传入的缓存对象
注意:分配内存后,缓存对象大小和flags不可变,想改变必须删除后重新创建缓存对象
- flags标志
标志 | 说明 |
---|---|
GL_DYNAMIC_STORAGE_BIG | 缓存内容可直接更新 |
GL_MAP_READ_BIG | 缓冲数据库可映射以便读取 |
GL_MAP_WRITE_BIG | 缓冲数据可映射以便写入 |
GL_MAP_PERSISTENT_BIT | 缓冲数据库可持久映射 |
GL_MAP_COHERENT_BIG | 缓冲映射图是连贯的 |
GL_CLIENT_STORAGE_BIT | 如果其他条件都满足,则优先选择将存储放在本地客户端而不是服务器 |
- 演示代码
GLuint buffer;
glCreateBuffers(1, &buffer);
glBindBuffer(GL_UNIFORM_BUFFER, buffer);
glBufferStorage(GL_UNIFORM_BUFFER, 1024 * 1024, nullptr, GL_MAP_WRITE_BIT);
♥ 更新数据
缓存对象建立后我们可以通过下列几种方法去更新数据
♣ BufferSubData
void glBufferSubData(GLenum target, GLintptr offest, GLSizeiptr size, const GLvoid * data)
void glNamedBufferSubData(GLuint buffer, GLintptr offest, GLSizeiptr size, const GLvoid * data)
参数1:target
缓冲对象的类型, buffer
缓存对象
参数2:offest
地址偏移量
参数3:size
数据大小
参数4:data
指更新数据
glBufferSubData
接口作用于某个类型的缓存对象,glNamedBufferSubData
直接作用于传入的缓存对象
- 演示代码
static const GLfloat vertex_data[] =
{
-0.25f, -0.25f, 0.25f, 0.0f, 1.0f,
-0.25f, -0.25f, -0.25f, 0.0f, 0.0f,
0.25f, -0.25f, -0.25f, 1.0f, 0.0f,
}
glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(data), &data);
♣ MapBuffer
还有一种更高效的方式更新缓存对象的数据,直接向OpenGL请求一个由缓存对象表示的指针放入内存,然后拷贝数据,这就是映射缓冲
,如下:
void glMapBuffer(GLenum target, GLenum usage);
void glMapNamedBuffer(GLuint buffer, GLenum usage);
参数1:target
缓冲对象的类型, buffer
缓存对象
参数2:usage
访问模式
- 取消映射缓冲接口
void glUnMapBuffer(GLenum target);
void glUnMapNamedBuffer(GLuint buffer);
参数1:target
缓冲对象的类型, buffer
缓存对象
- 演示代码
static const GLfloat vertex_data[] =
{
-0.25f, -0.25f, 0.25f, 0.0f, 1.0f,
-0.25f, -0.25f, -0.25f, 0.0f, 0.0f,
0.25f, -0.25f, -0.25f, 1.0f, 0.0f,
}
void* ptr = glMapNamedBuffer(buffer, GL_WRITE_ONLY);
memcpy(ptr, data, sizeof(data));
glUNmapNamedBuffer(GL_APPAY_BUFFER);
- usage访问模式展示
标志 | 说明 |
---|---|
GL_READ_ONLY | 对映射的内存进行只读操作 |
GL_WRITE_ONLY | 对映射的内存进行只写操作 |
GL_READ_WRITE | 对映射的内存进行读或写操作 |
♣ MapBufferRange
void glMapBufferRange(GLenum target, GLintptr offest, GLsizeiptr length, GLbitfield access);
void glMapNamedBufferRange(GLuint buffer, GLintptr offest, GLsizeiptr length, GLbitfield access);
参数1:target
缓冲对象的类型, buffer
缓存对象
参数2:offest
要映射的范围的缓冲区内的起始偏移量
参数3:length
映射的范围的长度
参数4:access
指定访问标志的组合,指示对范围的期望访问
注意:只映射特定范围内的缓存对象
♠ 填充以及拷贝数据到缓冲
♥ 填充常数值
根据书中介绍,如果要填充到缓冲区一个常数值,使用glCearBufferSubData和glClearNamedBufferSubData更有效
void glClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void * data);
void glClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void * data);
参数1:target
缓冲对象的类型, buffer
缓存对象
参数2:internalformat
指定缓冲区对象中的数据的内部存储格式
参数3:offset
需要替换数据的偏移量
参数4:size
指定填充的数据的大小
参数5:format
指定内存中的数据的格式
参数6:type
指定的数据类型
参数7:data
用来替换掉缓冲区对象中的数据
♥ 缓存之间复制数据
我们可能需要在多个缓存之间复制数据
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
void glCopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
参数1:readXXX
复制的缓存类型或缓存
参数2:wirteXXX
赋值的缓存类型或缓存
参数3:readoffset
读取数据的位置
参数4:size
读取数据的大小
♥ 使用缓冲为顶点着色器提供数据
前面的章节我们听提及到顶点数组对象(VAO)
我们创建好VAO后,可以依赖顶点的属性值,要求OpenGL使用存储在缓存对象中的数据自动填充,步骤分为以下几个
♥ 将缓存绑定给VAO
我们使用glVertexArrayVertexBuffer
接口来将缓存绑定到VAO中
glVertexArrayVertexBuffer(GLuint vao,GLuint bindingIndex, GLuint buffer, GLuintprt offset, GLsizei stride)
参数1:vao
创建好的顶点数组对象
参数2:bindingIndex
顶点缓存的索引
参数3:buffer
缓存对象
参数4:offset
顶点数据开始的位置
参数5:stride
每个顶点之间的距离
♥ 描述数据的布局和格式
我们使用glVertexArrayAttribFormat
接口来将缓存绑定到VAO中
glVertexArrayAttribFormat(GLuint vao, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset)
参数1:vao
创建好的顶点数组对象
参数2:attribindex
顶点缓存的索引
参数3:size
表示存储在缓存中的每个顶点的分量数量
参数4:type
数据的类型
参数5:normalized
数据是否应该标准化
参数5:relativeoffset
特定属性数据起点处顶点数据的偏移量
♥ 设置顶点属性来引用缓存的绑定
glVertexArrayAttribBinding(GLuint vao, GLuint attribindex, GLuint bindingindex)
参数1:vao
创建好的顶点数组对象
参数2:attribindex
对应类型顶点数据的索引
参数3:bindingindex
缓存对象的索引
vao被绑定后,attribindex对应的顶点属性应该从bindingindex对应绑定的缓存中获取数据
♥ 演示代码
GLuint buffers[2];
glCreateBuffers(2, &buffers[0]);
glNamedBufferStorage(buffers[0], sizeof(vertices), vertices, 0);
glNamedBufferStorage(buffers[1], sizeof(colors), colors, 0);
//顶点位置数据
glVertexArrayVertexBuffer(vao, 0, buffers[0], 0, 3*sizeof(float));
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 0, 0);
glEnableVertexArrayAttrib(vao, 0);
//顶点颜色数据
glVertexArrayVertexBuffer(vao, 1, buffers[1], 0, 3 * sizeof(float));
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 1, 1);
glEnableVertexArrayAttrib(vao, 1);
♠ 整体的演示效果
书中给的代码和官方示例里的例子有很多API存在差异,这让人相当的困扰,博主在这里也只是对流程和步骤做了理解,下面粘一对代码,通过缓冲数据给顶点着色器赋值,画出一个三角形,仅供参考
#include <sb7.h>
class test3_OpenGL : public sb7::application
{
virtual void startup()
{
// 获取program
program = compile_shaders();
// 创建和绑定顶点数组
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// 三角形位置信息
float vertex_positions[] = {
0.25, -0.25, 0.5,
-0.25, -0.25, 0.5,
0.25, 0.25, 0.5
};
// 创建缓冲对象
GLuint position_buffer;
glGenBuffers(1, &position_buffer);
// 绑定顶点类型
glBindBuffer(GL_ARRAY_BUFFER, position_buffer);
// 分配内存
glBufferData(GL_ARRAY_BUFFER,
sizeof(vertex_positions),
vertex_positions,
GL_STATIC_DRAW);
// 指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
// 启用顶点属性
glEnableVertexAttribArray(0);
}
virtual void render(double currentTime)
{
static const GLfloat green[] = { 0.0f, 0.25f, 0.0f, 1.0f };
glClearBufferfv(GL_COLOR, 0, green);
glPointSize(200);
glUseProgram(program);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
/
// func:编写一个简单的着色器
/
GLuint compile_shaders(void)
{
GLuint vertex_shader;
GLuint fragment_shader;
GLuint program;
//顶点着色器
static const char * vs_source[] =
{
"#version 420 core \n"
" \n"
"layout (location = 0) in vec3 aPos; \n"
"void main(void) \n"
"{ \n"
" \n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); \n"
"} \n"
};
//片段着色器
static const char * fs_source[] =
{
"#version 420 core \n"
" \n"
"out vec4 color; \n"
" \n"
"void main(void) \n"
"{ \n"
" color = vec4(0.0, 0.8, 1.0, 1.0); \n"
"} \n"
};
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, vs_source, NULL);
glCompileShader(vertex_shader);
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, fs_source, NULL);
glCompileShader(fragment_shader);
program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return program;
}
void shutdown()
{
glDeleteVertexArrays(1, &vao);
glDeleteProgram(program);
}
private:
GLuint program;
GLuint vao;
};
DECLARE_MAIN(test3_OpenGL)
♠ 推送
- Github
https://github.com/KingSun5
♠ 结语
本章因为书中接口和测试用例因为OpenGL版本存在些许差异,会让人相当困扰,最好有书和官方示例两相结合阅览,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。