当前位置: 首页 > java >正文

【OpenGL】LearnOpenGL学习笔记18 - Uniform缓冲对象UBO

上接:https://blog.csdn.net/weixin_44506615/article/details/150962393?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL

在接触Uniform缓冲对象之前,我们先来扩充一些数据和GLSL方面的知识

一、glBufferSubData

我们之前使用 glBufferData 来为缓冲区填充数据,它一次性会填充整个缓冲区,如果我们想为缓冲区的特定区域进行填充,也就是填充一部分,就可以用到 glBufferSubData

// 填充范围 [24, 24 + sizeof(data)]
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data);

二、glMapBuffer

我们还可以直接获取到缓冲指针来直接操作缓冲

float data[] = {...
};glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 获取指针
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 复制数据到缓冲
memcpy(ptr, data, sizeof(data));
// 归还指针
glUnmapBuffer(GL_ARRAY_BUFFER);

三、分批顶点属性

目前我们顶点属性的排布是进行了交错处理的,例如位置-法线-UV我们是按1112223311122233这样来排列
而我们在加载模型之后获得的三种数据往往是分离的,如果不想手动进行交错组装,那么我们可以采用分批的方式进行排列:1111112222223333

float positions[] = { ... };
float normals[] = { ... };
float uvs[] = { ... };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(uvs), &uvs);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)));

不管使用交错还是分批的方式进行排布都是可行的,不过还是推荐使用交错方法,这样一来每个顶点着色器运行的时候所需要的顶点属性在内存中会更加紧密的对齐

四、glCopyBufferSubData

我们可以通过 glCopyBufferSubData 来将一个缓冲的数据复制到另一个缓冲中

// readtarget 复制源
// writetarget 复制目标
// readoffset 读偏移
// writeoffset 写偏移
// 复制大小
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
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));

五、GLSL内置变量

1. gl_PointSize
顶点着色器中使用
在图元是GL_POINTS的时候,我们可以通过gl_PointSize来设置渲染点的大小

glEnable(GL_PROGRAM_POINT_SIZE);
void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);    gl_PointSize = gl_Position.z;    
}

当我们离点越远,它们会变得更大
gl_PointSize

2. gl_VertexID
顶点着色器中使用
当使用 glDrawElements 进行绘制的时候,这个变量会存储正在绘制顶点的索引
当使用 glDrawArrays 进行绘制的时候,这个变量会存储从渲染调用开始的已处理顶点的数量

3. gl_FragCoord
片段着色器中使用
gl_FragCoord 是一个vec3向量,其x、y表示该片段在屏幕空间中的坐标,z表示深度
通过x、y我们可以实现像是左边显示法线、右边显示渲染结果这样的效果 (在一些画面对比、技术演示中常用到)

if (gl_FragCoord.x >= 400)FragColor = vec4(result, 1.0);
elseFragColor = vec4(normal, 1.0);

gl_FragCoord
4. gl_FrontFacing
片段着色器中使用
gl_FrontFacing 是一个bool类型变量,它可以告诉我们当前片段是属于正向面还是反向面的一部分,不过如果开了面剔除那就没有意义了

5. gl_FragDepth
片段着色器中使用
gl_FragCoord.z 可以获得深度值,而 gl_FragDepth 可以设置深度值,如果我们没有主动设置的话,它会自动取用 gl_FragCoord.z 的值

// 设置该片段深度值为0
gl_FragDepth = 0.0;

如果我们设置了深度值,OpenGL会禁用所有的提前深度测试,这是因为它无法在片段着色器之前就知道片段的深度值
不过在OpenGL 4.2 起,我们可以通过设置深度条件来达到

// condition:
// any: 默认值,提前深度测试是禁用的,你会损失很多性能
// greater: 你只能让深度值比 gl_FragCoord.z 更大
// less: 你只能让深度值比 gl_FragCoord.z 更小
// unchanged: 如果你要写入gl_FragDepth,你将只能写入gl_FragCoord.z的值
layout (depth_<condition>) out float gl_FragDepth;

六、接口块

之前我们从顶点着色器向片段着色器发送数据都是使用的 out XXX xx 这样来定义的,GLSL还提供了一种叫 接口块 (Interface Block) 的东西,它类似结构体,可以让我们成块的输出和输入

// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;// 声明输出接口块
out VS_OUT
{vec2 TexCoords;
} vs_out;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);    vs_out.TexCoords = aTexCoords;
}  // 片段着色器
#version 330 core
out vec4 FragColor;// 声明同名输入接口块
in VS_OUT
{vec2 TexCoords;
} fs_in;uniform sampler2D texture;void main()
{             FragColor = texture(texture, fs_in.TexCoords);   
}

Uniform缓冲对象 (UBO)

一些扩充知识介绍完,终于到了Uniform缓冲对象 (UBO) 了
到目前为止,我们创建的不同Shader,每一个需要用到VP矩阵的Shader都需要单独去传入uniform变量
而UBO则可以让我们定义在多个着色器程序相同全局Uniform变量

UBO仍然是一个缓冲对象,所以我们可以通过 glGenBuffers 来创建它,并把它绑定到 GL_UNIFORM_BUFFER 缓冲目标上,对于用到的着色器中,我们需要像以下这样编写

#version 330 core
layout (location = 0) in vec3 aPos;layout (std140) uniform Matrices
{mat4 projection;mat4 view;
};uniform mat4 model;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);
}

我们声明了一个叫做MatricesUniform块,但是我们访问时不需要带上块名
对于 layout (std140) 则表示该块用到的Uniform块布局

Uniform块布局就是Uniform变量在缓冲中的排列方式,除此之外还有 sharedpackedstd430 等,详情可以看这里

绑定点 (Binding Point)

目前,我们可以像这样创建并绑定UBO

unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
// 分配152字节的内存
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); 
glBindBuffer(GL_UNIFORM_BUFFER, 0);

但是我们要怎样将Shader中声明的Uniform块和UBO联系起来呢? 这就要用到绑定点
在OpenGL上下文中定义了一系列的绑定点,我们可以将一个UBO与它链接起来,并将Shader中的Uniform块与它链接起来,如下图所示 (图片来自于LearnOpenGL)
绑定点
可以像这样链接Uniform块和绑定点

// 将shader中的Matrices Uniform块绑定到绑定点1
unsigned int matrices_idx = glGetUniformBlockIndex(shader.ProgramID, "Matrices");   
glUniformBlockBinding(shader.ProgramID, matrices_idx, 1);

在 OpenGL 4.2 版本起,可以通过布局标识符来链接

layout(std140, binding = 1) uniform Matrices { ... };

然后链接UBO和绑定点

// (绑定目标, 绑定点, 缓冲)
glBindBufferBase(GL_UNIFORM_BUFFER, 1, uboExampleBlock); 
// 或
// (绑定目标, 绑定点, 缓冲, 偏移, 大小)
glBindBufferRange(GL_UNIFORM_BUFFER, 1, uboExampleBlock, 0, 152);

使用Uniform缓冲对象

接下来可以修改一下我们的代码,将VP矩阵移到UBO中

首先修改着色器,我们需要修改所有需要用到UBO的顶点着色器,这里以背包为例
VertexShader.glsl

#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out vec3 Position;uniform mat4 model;
// 声明Uniform块
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);FragPos = vec3(model * vec4(aPos, 1.0));Normal = mat3(transpose(inverse(model))) * aNormal;TexCoords = aTexCoords;Position = vec3(model * vec4(aPos, 1.0));
}

接下来创建UBO缓冲,预留 2 * sizeof(glm::mat4) 大小的空间,并链接到绑定点0
Main.cpp

unsigned int uboMaterices;
glGenBuffers(1, &uboMaterices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMaterices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMaterices, 0, 2 * sizeof(glm::mat4));

然后链接Uniform块与绑定点

// 定义宏辅助
#define BindMatericesBlock(NAME,SHADER_VAR, SLOT) unsigned int NAME##MatricesUniformBlockIdx = glGetUniformBlockIndex(SHADER_VAR.ProgramID, "Materices"); \
glUniformBlockBinding(SHADER_VAR.ProgramID, NAME##MatricesUniformBlockIdx, SLOT);BindMatericesBlock(bag, shader, 0);
BindMatericesBlock(reflect, reflectShader, 0);
BindMatericesBlock(refract, refractShader, 0);
BindMatericesBlock(edge, edgeShader, 0);
BindMatericesBlock(grass, grassShader, 0);
BindMatericesBlock(window, windowShader, 0);

最后,更新数据到UBO

// 主循环
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::mat4(1);
projection = glm::perspective(glm::radians(camera.fov), screenWidth / screenHeight, camera.near, camera.far);glBindBuffer(GL_UNIFORM_BUFFER, uboMaterices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(view));
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(projection));
glBindBuffer(GL_UNIFORM_BUFFER, 0);

编译运行,检查我们所看见的图像,它应该和之前没有任何差别
稍微整理一下代码,完整代码可在顶部git仓库中找到

http://www.xdnf.cn/news/19444.html

相关文章:

  • 模型系列(篇三)-Llama
  • vscode克隆远程代码步骤
  • 合约服务架构-OOP 方式
  • leetcode 371 两个整数之和
  • 微软开源TTS模型VibeVoice,可生成 90 分钟4人语音
  • TFS-1996《The Possibilistic C-Means Algorithm: Insights and Recommendations》
  • 一些八股总结
  • 如何快速学习新技能
  • Redis 7.0 高性能缓存架构设计与优化
  • [Android] UI进阶笔记:从 Toolbar 到可折叠标题栏的完整实战
  • IDEA插件ApifoxHelper
  • C++ 登录状态机项目知识笔记
  • Nginx虚拟主机配置
  • CTFshow系列——命令执行web69-72
  • 数据结构 04(线性:双向链表)
  • 【大前端】React配置配置 开发(development)、生产(production)、测试(test)环境
  • 学习数据结构(15)插入排序+选择排序(上)
  • 算法——链表
  • 开源协作白板 – 轻量级多用户实时协作白板系统 – 支持多用户绘图、文字编辑、图片处理
  • 进程间通信IPC(interprocess communicate)
  • Introduction to GIS —— Chapter 4(Raster Data Model)
  • 解读IEC 60529-2013
  • MySQL 公用表达式
  • AI军团协同作战:Manus Wide Research深度解析
  • CAN数据链路层、网络层(ISO11898、15765)
  • JVM-指针压缩
  • Day 01(02): 精读HDFS概念
  • PortSwigger靶场之DOM XSS in document.write sink using source location.search通关秘籍
  • 多线程使用场景一(es数据批量导入)
  • 使用node-red+opencv+mqtt实现相机图像云端查看