OpenGL:Uniform Block
在使用着色器的时候,会出现一个问题,很多着色器会共享相同的 uniform 数据,比如projection, view 矩阵,这无形中对于性能是一种浪费。在OpenGL中,提供了一种方式,Uniform Block 可以解决这个问题。
Uniform Block Object
Uniform Block 也是一种 buffer ,和 VBO, EBO 一样,将数据绑定到上面去,然后就可以使用,所以可以将它称之为 UBO 。
将共享数据存储在一个与特定 binding point 关联的 UBO 缓冲对象 之后,在需要使用该共享数据的着色器中,只需要声明一个与该 binding point 关联的 uniform block 。这样,所有链接到同一个 binding point 的不同着色器程序都可以自动访问该 UBO 中的共享数据 。之后只需要更新 UBO 缓冲区本身一次(例如在投影或视图矩阵变化时),所有关联的着色器就都能获取到新数据,**无需再使用 glUniform* 函数对_每个_着色器程序单独设置这些 uniform 值。因此 传输操作的次数显著减少,特别是当很多着色器共享相同数据时。”
GLuint ubo;
glGenBuffers(1, &ubo);
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
// 这里不填充数据,只是为了在GPU设置一片区域用于存储 uniform block的数据
glBufferData(GL_UNIFORM_BUFFER, sizeof(uniform block), nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
Uniform Block Layout
在 uniform block 的使用中,理解其内存布局是实现 CPU 端与 GPU 端数据正确匹配的关键问题。OpenGL 提供了几种布局规范,其中 std140 是最常用的标准:
layout (std140) uniform Matrices {mat4 projection;mat4 view;
};
对于 std140 的数据布局规则,每种数据类型都有它自己的 base alignment,在 block 中的数据还有一个相对于block 开始位置的偏移,这个偏移量的计算需要用到每种数据类型的 base alignment。下面是每种数据类型的布局规则。每4个字节用一个 N 表示。
| 类型 | 布局规则 |
|---|---|
| 标量,比如int和bool | 每个标量的基准对齐量为N。 |
| 向量 | 2N或者4N。这意味着vec3的基准对齐量为4N。 |
| 标量或向量的数组 | 每个元素的基准对齐量与vec4的相同。 |
| 矩阵 | 储存为列向量的数组,每个向量的基准对齐量与vec4的相同。 |
| 结构体 | 等于所有元素根据规则计算后的大小,但会填充到vec4大小的倍数。 |
计算的一个例子:
layout (std140) uniform ExampleBlock {// 基准对齐量 // 对齐偏移量float value; // 4 // 0 vec3 vector; // 16 // 16 (必须是16的倍数,所以 4->16)mat4 matrix; // 16 // 32 (列 0)// 16 // 48 (列 1)// 16 // 64 (列 2)// 16 // 80 (列 3)float values[3]; // 16 // 96 (values[0])// 16 // 112 (values[1])// 16 // 128 (values[2])bool boolean; // 4 // 144int integer; // 4 // 148
};
如何使用
- 创建
UBO,分配GPU空间
glGenBuffers(1, &uboMatrices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
- 绑定
UBO到指定的绑定点
// 将 uboMatrices 绑定到0号绑定点
glBindBufferBase(GL_UNIFORM_BUFFER, 1, uboMatrices);
// 也可以用下面的函数进行绑定,
glBindBufferRange(GL_UNIFORM_BUFFER, 1, uboMatrices, 0, 2 * sizeof(glm::mat4));
void glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size):指定位置index,绑定buffer到target上,数据从offset开始,大小为size。
- 将着色器中需要使用到的共享
uniform数据也绑定到和UBO相同的绑定点。
// 可以先获取到原来的绑定点
GLuint bindingIndex = glGetUniformBlockIndex(shader.ID, "Matrices");
// 然后当前的绑定点换到 UBO 所绑定的绑定点上(上一步是绑定到1位置,所以这里也是写0位置)
glUniformBlockBinding(shader.ID, bingdingIndex, 1);
在OpenGL4.2开始,可以GLSL中直接指定绑定点:
layout (std140, binding=1) uniform Matrices {mat4 projection;mat4 view;
};
在使用Sampler的时候也是一样可以这样绑定的
layout (binding = 0) uniform sampler2D samp;
- 将数据传入到
UBO中
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(pMat));
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(vMat));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
