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

news/2024/11/18 12:35:10/
缓冲

前言
本篇在讲什么

关于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(){// 获取programprogram = 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.ppmy.cn/news/12830.html

相关文章

PHP MySQL 插入多条数据

使用 MySQLi 和 PDO 向 MySQL 插入多条数据 mysqli_multi_query() 函数可用来执行多条SQL语句。 以下实例向 "MyGuests" 表添加了三条新的记录: 实例 (MySQLi - 面向对象) <?php $servername "localhost"; $username "username"; $pas…

C++入门:命名空间

目录 一.前言 C关键字(C98)总览&#xff1a; 一.作用域 二.命名冲突 三.命名空间 命名空间定义&#xff1a; 命名空间的嵌套定义&#xff1a; 四.命名空间的使用 五.命名空间的本质 一.前言 C是从C语言延伸出来的编程语言&#xff0c;C兼容了C语言百分之九十九的语法…

linux服务管理

1.service 用法&#xff1a;service 服务名 start/stop/restart 实例&#xff1a; 重启网络服务&#xff1a; #service network retart 关闭ftp服务&#xff1a; #service vsftpd stop 打开httpd服务 #service httpd start 2.chkconfig 功能&#xff1a;能够为不同的系统级别设…

nginx 伪静态 Rewrite 正则

正则表达式匹配 &#xff1a; ~ 为区分大小写匹配~* 为不区分大小写匹配!~ 和 !~*分别为区分大小写不匹配及不区分大小写不匹配 文件及目录匹配&#xff0c;其中&#xff1a; -f和!-f用来判断是否存在文件-d和!-d用来判断是否存在目录-e和!-e用来判断是否存在文件或目录-x和!-x…

第一层:封装

文章目录前言类和对象封装class权限publicprotectedprivatestruct和class的区别封装的好处封装的用法学完封装&#xff0c;突破第一层&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;一名大一的智能制造专业学生&#xff0c;在学习C/C的路上会越走越远&#xff0c…

算法记录Day52|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

一、300.最长递增子序列 1.题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] …

【AcWing周赛】AcWing第86场周赛

目录 <一>AcWing 4794. 健身 一、题目 1、原题链接 2、题目描述 二、解题报告 1、思路分析 2、时间复杂度 3、代码详解 <二>AcWing 4795. 安全区域 一、题目 1、原题链接 2、题目描述 二、解题报告 1、思路分析 2、时间复杂度 3、代码详解 <三…

Pandas 数据结构 - Series

前言Pandas Series 类似表格中的一个列&#xff08;column&#xff09;&#xff0c;类似于一维数组&#xff0c;可以保存任何数据类型。Series 由索引&#xff08;index&#xff09;和列组成&#xff0c;函数如下&#xff1a;pandas.Series( data, index, dtype, name, copy)参…