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

news2024/11/25 3:03:54
缓冲

前言
本篇在讲什么

关于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 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版本存在些许差异,会让人相当困扰,最好有书和官方示例两相结合阅览,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。

👉 本文属于原创文章,转载请评论留言,并在转载文章头部著名作者出处👈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/168250.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用OpenCV透视变换技术实现坐标变换实践

1. 概述 1.1. 需求 在局部空间&#xff08;无GPS定位&#xff09;视频监控过程中&#xff0c;把视频识别到物体位置&#xff0c;投射到空间平面坐标系中&#xff0c;获取物体在局部空间的平面坐标。 1.2. 解决方案 使用图像透视变换技术。 1.3. 透视变换概念 透视变换是指…

链路追踪组件Skywalking使用

前言Skywalking是一个国产开源框架&#xff0c;2015年由吴晟开源 &#xff0c; 2017年加入Apache孵化器&#xff0c;其用于追踪多微服务模块调用之间日志的追踪&#xff0c;协助程序员进行排除问题Skywalking架构Skywalking架构图大概如下SkyWalking OAP&#xff1a; SkyWalkin…

如何格式化U盘?以及优盘格式化的恢复方法

我们经常使用U盘来进行存储和传输数据&#xff0c;使用时间久了&#xff0c;可能需要我们进行优盘格式化。需要注意&#xff01;优盘格式化之前&#xff0c;记得对里面的数据进行备份&#xff0c;防止数据清空造成不必要的损失。 如何进行U盘格式化&#xff1f;格式化优盘后&a…

“深度学习”学习日记。误差反向传播法--加法层、乘法层、激活函数层的实现

2023.1.16 1、加法层、乘法层&#xff1a; 前两篇文章都在讲述理论&#xff0c;今天实现代码操作&#xff1a;关于加法节点&#xff0c;乘法节点的内容在这篇文章。 https://blog.csdn.net/m0_72675651/article/details/128695488 在以后的学习中&#xff0c;将把构建神经网…

AHOcoder声码器

AHOcoder声码器 目前最常见的声码器有WORLD&#xff0c;STRAIGHT&#xff0c;&#xff27;riffin_Lim等&#xff0c;AHocoder算是少见的&#xff0c;但也可以学习一下。 代码下载网址&#xff1a;AHOcoder 简介 AHOcoder 语音声码器由 Daniel Erro 在巴斯克大学的 AHOLAB 信…

若依配置教程(一)运行若依系统

一、下载源代码 若依的源代码是开源的&#xff0c;所以我们在若依的官方网站即可进行下载&#xff0c;若依的官网是&#xff1a;http://doc.ruoyi.vip/ruoyi-vue/&#xff0c;进入官网后&#xff0c;会显示代码下载的地址&#xff1a;https://gitee.com/y_project/RuoYi-Vue&a…

【进阶】Bean作用域和生命周期

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录一、通过⼀个案例来看 Bean 作⽤域的问题1. 被修改的Bean案例2. 原因分析二、作用域Scope定义1. Bean的六种作用域&#xff08;重点&#xff09;1&#xff09;singleton2&#xff09;prototype3&#xff09;request4…

JVM——类加载与字节码技术(2)

三、编译期处理 所谓的 语法糖 &#xff0c;其实就是指 java 编译器把* .java 源码编译为* .class 字节码的过程中&#xff0c;自动生成和转换的一些代码&#xff0c;主要是为了减轻程序员的负担&#xff0c;算是 java 编译器给我们的一个额外福利 【注意】以下代码的分析&am…

Python爬虫之基于 selenium 实现文献信息获取

目录初识 selenium网页分析代码实现踩过的坑最近有小伙伴后台跟我说&#xff0c;临近毕业&#xff0c;写毕业论文需要上知网查找大量的文献&#xff0c;但是一篇一篇看文献信息以及文献摘要又有点麻烦&#xff0c;能不能让我写一个爬虫去批量获取文献相关信息 我一听好家伙&am…

【算法】二叉树遍历

目录1.概述2.代码实现2.1.二叉树定义2.2.前序遍历2.3.中序遍历2.4.后序遍历2.5.层序遍历3.应用本文参考&#xff1a; LABULADONG 的算法网站 1.概述 &#xff08;1&#xff09;所谓遍历 (Traversal) 是指沿着某条搜索路线&#xff0c;依次对树中每个结点均做一次且仅做一次访问…

《从零开始编写一个直播服务器》 C++ 实现一个最简单的HTTP-FLV流媒体服务器

流媒体服务系列文章 文章目录流媒体服务系列文章前言一、http flv&#xff1f;二、使用步骤服务器代码总结前言 HTTP FLV通过http传输&#xff0c;时延可控制在2秒以内&#xff0c;浏览器可基于bilibili开源的flv.js(采用h5 mse技术)开发&#xff0c;比起rtsp、rtmp等免插件播…

Spring BeanPostProcessor

BeanPostProcessor&#xff0c;是bean的增强器&#xff0c;在bean初始化前后调用&#xff0c;常用的方法有postProcessBeforeInitialization和postProcessAfterInitialization&#xff0c;在Spring启动并初始化bean前后通过它们做一些扩展操作。 1、BeanPostProcessor 接口说明…

【信管9.1】​项目沟通及过程

项目沟通及过程沟通这个东西&#xff0c;可以说是整个项目成功失败最关键的因素。9成以上失败的项目在最后总结的时候&#xff0c;沟通不畅或者信息对接问题都会占据前三甲。其实只要是做项目&#xff0c;那么必须有团队&#xff0c;有团队有人&#xff0c;那么沟通就是不可避免…

03 技术太卷我学APEX-关于blob数据类型的使用

03 技术太卷我学APEX-关于blob数据类型的使用 0 Oracle 的blob类型 BLOB BLOB全称为二进制大型对象&#xff08;Binary Large Object)。它用于存储数据库中的大型二进制对象。可存储的最大大小为4G字节。 通常像图片、文件、音乐等信息就用BLOB字段来存储&#xff0c;先将文件…

外贸软件成本核算丨采购出入库有磅差怎么办

在液化天然气油料等行业&#xff0c;在与供应商之间的进出口贸易过程中&#xff0c;总是少不了会出现磅差的情况&#xff0c;因此就需要有磅差的约定。那什么是磅差呢&#xff1f;磅差指的是&#xff0c;供应方在发货时提供的磅单与购买方实际验收过磅数量之间的差额。一般磅差…

C++设计模式(1)——单例模式

亦称&#xff1a;单件模式、Singleton 意图 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 问题 单例模式同时解决了两个问题&#xff0c; 所以违反了单一职责原则&#xff1a; 1、保证一个类只…

CSS给元素添加边框(样式、颜色、宽度)

给元素添加边框 CSS边框属性允许你指定一个元素边框的样式和颜色, 和边框宽度。 可以使用 border 属性将边框样式,颜色,和宽度 一起设置。 如果不设置其中的某个值&#xff0c;也不会出问题&#xff0c;比如 border: solid #ff0000; 也是允许的。 使用border-style属性设置边…

ORA-39002: 操作无效 ORA-39070: 无法打开日志文件

今天在oracle12c上导数据&#xff0c;出现了错误。导库脚本久经考验&#xff0c;不应该有什么问题&#xff0c;但就是报错了。错误开头2句是&#xff1a; ORA-39002: 操作无效 ORA-39070: 无法打开日志文件 网上搜来的结果&#xff0c;是存放导出文件的路径不对&#xff0c;就…

常用API(String、ArrayList)

API&#xff08;应用程序接口&#xff09; Java写好的技术&#xff08;功能代码&#xff09;&#xff0c;可以直接调用String概述java.lang.String类代表字符串&#xff0c;String类定义的变量可以用于指向字符串对象&#xff0c;然后操作该字符串Java程序中的所有字符串文字&a…

JavaEE进阶第三课:Spring更简单的对象存储和取出(上)

上篇文章介绍了Spring的创建和使用&#xff0c;讲解3了Bean对象的基本存储和取出&#xff0c;这篇文章我们将会介绍Spring更简单的对象存储 目录1.Bean的存储1.0准备工作1.1五大类注解1.1.2为什么要有这么多注解1.2方法注解1.1.1方法注解需要搭配类注解一起使用1.2.2方法重载怎…