(第五章)OpenGL超级宝典学习:缓冲

缓冲

前言
本篇在讲什么

关于OpenGL数据缓冲的相关内容
本篇适合什么

适合初学OpenGL的小白
想了解OpenGL缓冲对象的同学

本篇需要什么

C++语法有简单认知
OpenGL有简单认知
最好是有OpenGL超级宝典蓝宝书
依赖Visual Studio编辑器

本篇的特色

具有全流程的图文教学
重实践,轻理论,快速上手
提供全流程的源码内容


★提高阅读体验★

👉 ♠ 一级标题 👈

👉 ♥ 二级标题 👈

👉 ♣ 三级标题 👈

👉 ♦ 四级标题 👈


♠ 创建缓冲和分配内存

♥ 创建缓存

我们通过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 TargetPurpose
GL_ARRAY_BUFFERVertex attributes
GL_ATOMIC_COUNTER_BUFFERAtomic counter storage
GL_COPY_READ_BUFFERBuffer copy source
GL_COPY_WRITE_BUFFERBuffer copy destination
GL_DISPATCH_INDIRECT_BUFFERIndirect compute dispatch commands
GL_DRAW_INDIRECT_BUFFERIndirect command arguments
GL_ELEMENT_ARRAY_BUFFERVertex array indices
GL_PIXEL_PACK_BUFFERPixel read target
GL_PIXEL_UNPACK_BUFFERTexture data source
GL_SHADER_STORAGE_BUFFERRead-write storage for shaders
GL_TEXTURE_BUFFERTexture data buffer
GL_TRANSFORM_FEEDBACK_BUFFERTransform feedback buffer
GL_UNIFORM_BUFFERUniform 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版本存在些许差异,会让人相当困扰,最好有书和官方示例两相结合阅览,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。

👉 本文属于原创文章,转载请评论留言,并在转载文章头部著名作者出处👈
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值