OpenGL之uniform block 与UBO

2023-10-14 14:10
文章标签 block opengl uniform ubo

本文主要是介绍OpenGL之uniform block 与UBO,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 原因

目前,我们在着色器中要传递多个uniform变量时,总是使用多个uniform,然后在主程序中设置这些变量的值;如果你的程序中包含了多个着色器,而且这些着色器使用了相同的Uniform变量,你就不得不为每个着色器分别管理这些变量。Uniform变量的location是在程序链接的时候产生的,因此Uniform变量的location会随着着色器的不同而发生变化。因此,这些Uniform变量的数据必须重新产生,然后应用到新的location上。这使我们写的shader会变得非常复杂,当shader中需要很多参数,维护和把这么多参数传给shader将会变得非常的耗费性能和精力。

而Uniform Block可以让uniform变量在不同的着色器程序中实现共用,使不同着色器间共享Uniform数据,有了Uniform Block,我们可以创建一个缓冲区(UBO )对象来存储这些Uniform变量的值,然后将缓冲区对象绑定到Uniform Block。当着色器程序改变的时候,只需要将同样的缓冲区对像重新绑定到在新的着色器中与之相关的Block即可。

  • interface block

interfac block是一组GLSL着色器里面的输入、输出、uniform等变量的集合,有一些类似于C语言中的struct,但是不像struct那样简单明了,还有一些其他的选项包含在里面。通过使用interface block,我们可以将着色器中的变量以组的形式来管理,这样书写更整洁。
interface block的声明形式为:

storage_qualifier block_name
{<define members here>
} instance_name;

其中storage_qualifier指明这个block的存储限定符,限定符可以使用in​, out​, uniform​, 或者buffer(GLSL4.3支持)等,block_name则给定名称,而instance_name给定实例名称。 

例如顶点着色器和片元着色器之间需要传递法向量、纹理坐标等变量,将他们封装到一个block中,代码显得更紧凑。顶点着色器中输出变量定义形式如下:

// 定义输出interface block
out VS_OUT
{vec3 FragPos;vec2 TextCoord;vec3 FragNormal;
}vs_out;

而在片元着色器中,要以相同的block_name接受,实例名称则可以不同,形式可以定义为:

// 定义输入interface block
in VS_OUT
{vec3 FragPos;vec2 TextCoord;vec3 FragNormal;
}fs_in;

如果指定了instance_name,则在片元着色器中引用这些变量时需要加上instance_name前缀,例如: 

   // 环境光成分vec3    ambient = light.ambient * vec3(texture(material.diffuseMap, fs_in.TextCoord));

反之如果没有指定instance_name,则这个block中的变量将和uniform一样是全局的,可以直接使用。如果没有给定instance_name,则需要注意,interface block中给定的变量名不要和uniform给定的重复,否则造成重定义错误,例如下面的定义将造成重定义错误:

uniform MatrixBlock
{mat4 projection;mat4 modelview;
};uniform vec3 modelview;  // 重定义错误 和MatrixBlock中冲突

interface block帮助我们合理组织着色器中的变量。 

  • uniform block

就是上面的interface block,使用uniform限定符定义

  • UBO(uniform buffer object)是什么

 UBO和VBO类似,用来存uniform数据的一块显存。就是下面代码里的这东西:

    GLuint ubo;glGenBuffers(1, &ubo);glBindBuffer(GL_UNIFORM_BUFFER, ubo);
  • UBO如何实现着色器之间共享变量

如何在多个着色器之间简洁的共享变量。uniform buffer的实现思路为: 在多个着色器中定义相同的uniform block,然后将这些相同uniform block绑定到同一个UBO,这样就实现了一个UBO被多个着色器的uniform block共用,这个UBO的数据更新了,就会更新多个着色器中的uniform数据。所谓的共享就是多个uniform block(相同定义)共享同一个UBO数据。

着色器中的uniform block和主程序中的UBO如何连接起来呢?

是通过OpenGL的绑定点(binding points)连接起来的,它们的关系如下图所示:

uniform buffer使用时,每个shader中定义的uniform block有一个索引,通过这个索引连接到OpenGL的绑定点x;而主程序中创建UBO,把这个UBO也连接到绑定点x,此后shader中的uniform block就和OpenGL中的UBO联系起来,我们在程序中操作UBO的数据,就能够在不同着色器之间共享了。例如上图中,着色器A和B定义的Matrices的索引都指向绑定点0,他们共享openGL的uboMatrices这个UBO的数据。同时着色器A的Lights和着色器B的Data,分别指向不同的UBO。

下面通过一个简单例子来熟悉UBO的使用。

Step1: 首先我们在顶点着色器中定义uniform block如下:

   #version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;uniform mat4 model; // 因为模型变换矩阵一般不能共享 所以单独列出来// 定义UBO
layout (std140) uniform Matrices
{mat4 projection;mat4 view;
};  // 这里没有定义instance name,则在使用时不需要指定instance namevoid main()
{gl_Position = projection * view * model * vec4(position, 1.0);
}

Step2 在主程序中设置着色器的uniform block索引指向到绑定点0:

   // step1 获取shader中 uniform buffer 的索引GLuint redShaderIndex = glGetUniformBlockIndex(redShader.programId, "Matrices");GLuint greeShaderIndex = glGetUniformBlockIndex(greenShader.programId, "Matrices");...// step2 设置shader中 uniform buffer 的索引到指定绑定点glUniformBlockBinding(redShader.programId, redShaderIndex, 0); // 绑定点为0glUniformBlockBinding(greenShader.programId, greeShaderIndex, 0);...

 Step3: 创建UBO,并绑定到绑定点0

   GLuint UBOId;glGenBuffers(1, &UBOId);glBindBuffer(GL_UNIFORM_BUFFER, UBOId);glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_DYNAMIC_DRAW); // 预分配空间glBindBuffer(GL_UNIFORM_BUFFER, 0);glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBOId, 0, 2 * sizeof(glm::mat4)); // 绑定点为0

Step4: 更新UBO中的数据
这里使用前面介绍的glBufferSubData更新UBO中数据,例如更新视变换矩阵如下: 

glm::mat4 view = camera.getViewMatrix(); // 视变换矩阵
glBindBuffer(GL_UNIFORM_BUFFER, UBOId);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);

到此结束,关于UBO内存布局会另起一篇文章

这篇关于OpenGL之uniform block 与UBO的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/211047

相关文章

[Linux Kernel Block Layer第一篇] block layer架构设计

目录 1. single queue架构 2. multi-queue架构(blk-mq)  3. 问题 随着SSD快速存储设备的发展,内核社区越发发现,存储的性能瓶颈从硬件存储设备转移到了内核block layer,主要因为当时的内核block layer是single hw queue的架构,导致cpu锁竞争问题严重,本文先提纲挈领的介绍内核block layer的架构演进,然

OPENGL顶点数组, glDrawArrays,glDrawElements

顶点数组, glDrawArrays,glDrawElements  前两天接触OpenGL ES的时候发现里面没有了熟悉的glBegin(), glEnd(),glVertex3f()函数,取而代之的是glDrawArrays()。有问题问google,终于找到答案:因为OpenGL ES是针对嵌入式设备这些对性能要求比较高的平台,因此把很多影响性能的函数都去掉了,上述的几个函数都被移除了。接

OpenGL ES学习总结:基础知识简介

什么是OpenGL ES? OpenGL ES (为OpenGL for Embedded System的缩写) 为适用于嵌入式系统的一个免费二维和三维图形库。 为桌面版本OpenGL 的一个子集。 OpenGL ES管道(Pipeline) OpenGL ES 1.x 的工序是固定的,称为Fix-Function Pipeline,可以想象一个带有很多控制开关的机器,尽管加工

OpenGL雾(fog)

使用fog步骤: 1. enable. glEnable(GL_FOG); // 使用雾气 2. 设置雾气颜色。glFogfv(GL_FOG_COLOR, fogColor); 3. 设置雾气的模式. glFogi(GL_FOG_MODE, GL_EXP); // 还可以选择GL_EXP2或GL_LINEAR 4. 设置雾的密度. glFogf(GL_FOG_DENSITY, 0

opengl纹理操作

我们在前一课中,学习了简单的像素操作,这意味着我们可以使用各种各样的BMP文件来丰富程序的显示效果,于是我们的OpenGL图形程序也不再像以前总是只显示几个多边形那样单调了。——但是这还不够。虽然我们可以将像素数据按照矩形进行缩小和放大,但是还不足以满足我们的要求。例如要将一幅世界地图绘制到一个球体表面,只使用glPixelZoom这样的函数来进行缩放显然是不够的。OpenGL纹理映射功能支持将

OpenGL ES 2.0渲染管线

http://codingnow.cn/opengles/1504.html Opengl es 2.0实现了可编程的图形管线,比起1.x的固定管线要复杂和灵活很多,由两部分规范组成:Opengl es 2.0 API规范和Opengl es着色语言规范。下图是Opengl es 2.0渲染管线,阴影部分是opengl es 2.0的可编程阶段。   1. 顶点着色器(Vert

block对变量捕获的方式

之前见很多文章对block捕获变量的方法,会进行诸如此类的描述:“block会捕获被引用的变量, 并对其进行copy操作, 因此, 可能会导致其引用计数加1,如果处理不好, 可能因循环引用导致内存泄漏。” 实际上, 这种说法并不严谨。block对变量的捕获, 根据变量类型的不同,会采用不同的捕获方式。 (1)静态或者全局变量, 在block中直接是指针传递的方式传入block中,对其进行的操作

OpenGL/GLUT实践:流体模拟——数值解法求解Navier-Stokes方程模拟二维流体(电子科技大学信软图形与动画Ⅱ实验)

源码见GitHub:A-UESTCer-s-Code 文章目录 1 实现效果2 实现过程2.1 流体模拟实现2.1.1 网格结构2.1.2 数据结构2.1.3 程序结构1) 更新速度场2) 更新密度值 2.1.4 实现效果 2.2 颜色设置2.2.1 颜色绘制2.2.2 颜色交互2.2.3 实现效果 2.3 障碍设置2.3.1 障碍定义2.3.2 障碍边界条件判定2.3.3 障碍实现2.3.

OpenGL——着色器画一个点

一、 绘制 在窗口中间画一个像素点: #include <GL/glew.h>#include <GLFW/glfw3.h>#include <iostream>using namespace std;#define numVAOs 1GLuint renderingProgram;GLuint vao[numVAOs];GLuintcreateShaderProgram (){c

Linux block_device gendisk和hd_struct到底是个啥关系

本文的源码版本是Linux 5.15版本,有图有真相: 1.先从块设备驱动说起 安卓平台有一个非常典型和重要的块设备驱动:zram,我们来看一下zram这个块设备驱动加载初始化和swapon的逻辑,完整梳理完这个逻辑将对Linux块设备驱动模型有深入的理解。 zram驱动加载的时候会调用zram_add函数,源码如下: 1887/*1888 * Allocate and initia