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               // 144
    int integer;     // 4               // 148
}; 

如何使用

  1. 创建 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);
  1. 绑定 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,绑定 buffertarget上,数据从 offset 开始,大小为 size
  1. 将着色器中需要使用到的共享 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;
  1. 将数据传入到 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);
<think> 我们正在讨论的是在OpenGL ES中如何将数组传递给uniform变量。根据引用内容,有几种方法可以实现,特别是当数组较大时。 引用[1]介绍了UBO(Uniform Buffer Object),它是一种将uniform变量数据存储在缓冲区对象中的方式,不占用着色器程序自身的uniform存储空间,需要与uniform块配合使用。 引用[2]展示了一个示例代码,其中有一个uniform数组`gauss_num`,它通过创建一个`osg::FloatArray`并设置到`osg::Uniform`对象来传递数组数据。 引用[3]提到三种传递大数组的方法:使用纹理(通过texelFetch获取)、UBO和TBO(纹理缓冲区对象)。 引用[4]和[5]虽然提到了数组传递,但分别是在WebAssembly和Python/MySQL的上下文中,与OpenGL ES无关,因此我们主要参考引用[1][2][3]。 用户的问题:如何将数组传入uniform? 根据引用,我们可以总结出两种主要方法(不考虑TBO,因为引用中主要提到UBO和纹理): 1. 直接使用uniform数组(适用于小数组) - 如引用[2]所示。 2. 使用UBO(Uniform Buffer Object)来传递大数组(如引用[1]和[3]所述)。 另外,引用[3]还提到使用纹理的方式,但这不是直接通过uniform传递数组,而是通过纹理采样来获取数据。 因此,我们重点介绍前两种方法。 方法一:直接设置uniform数组 步骤: a. 在着色器中声明一个uniform数组,例如:`uniform float myArray[10];` b. 在应用程序中,创建对应的数组数据(如引用[2]中的`osg::FloatArray`)。 c. 获取uniform的位置,并使用OpenGL ES的API(如`glUniform1fv`)设置数组。 注意:这种方法受限于着色器程序允许的uniform变量大小,对于大数组可能不适用。 方法二:使用UBO传递数组 步骤: a. 在着色器中定义一个uniform块(uniform block),并在其中声明数组。例如: layout(std140, binding = 0) uniform MyBlock { float myArray[100]; }; b. 在应用程序中,创建一个UBO,并将数组数据填充到缓冲区中。 c. 将UBO绑定到对应的绑定点(binding point)。 这样,着色器就可以通过访问uniform块中的数组来获取数据。 此外,引用[3]还提到了使用纹理的方式(TBO也可以考虑),但用户问题明确要求传入uniform,所以这里我们主要介绍UBO方法。 下面将详细展开这两种方法,并给出示例代码(伪代码或OpenGL ES代码)。 </think> 在 OpenGL ES 中,将数组传递给 uniform 变量主要有以下三种常用方法,根据数组大小和需求选择合适方案: ### 🔧 方法一:直接声明 uniform 数组(适合小数组) 1. **着色器中声明** 在 GLSL 中直接定义数组长度: ```glsl uniform float myArray[10]; // 固定长度数组 ``` 2. **应用程序设置** 使用 `glUniform` 系列函数传递数据: ```cpp float data[10] = {...}; // 数组数据 GLint location = glGetUniformLocation(program, "myArray"); glUniform1fv(location, 10, data); // 一次性传递整个数组 ``` **注意**:数组大小必须与着色器声明完全匹配 [^2]。 ### 📦 方法二:使用 UBO(Uniform Buffer Object,适合大数组) 1. **着色器中定义 uniform 块** ```glsl layout(std140, binding = 0) uniform MyBlock { float largeArray[100]; }; ``` 2. **应用程序操作 UBO** ```cpp // 创建 UBO GLuint ubo; glGenBuffers(1, &ubo); glBindBuffer(GL_UNIFORM_BUFFER, ubo); // 填充数据(std140 对齐规则) float largeData[100] = {...}; glBufferData(GL_UNIFORM_BUFFER, sizeof(largeData), largeData, GL_STATIC_DRAW); // 绑定到绑定点 glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo); ``` **优势**:不占用着色器自身 uniform 空间,适合传递大型数据 [^1][^3]。 ### 方法三:通过纹理传递(适合超大数组) 1. **创建数据纹理** ```cpp GLuint texBuffer; glGenTextures(1, &texBuffer); glBindTexture(GL_TEXTURE_2D, texBuffer); glTexImage2D(..., GL_R32F, width, height, 0, GL_RED, GL_FLOAT, data); ``` 2. **着色器中采样读取** ```glsl uniform sampler2D dataTexture; float value = texelFetch(dataTexture, ivec2(index, 0), 0).r; ``` **适用场景**:当数组超过 UBO 大小限制时(OpenGL ES 3.0+)[^3]。 --- ### ⚠️ 关键注意事项 1. **数组长度一致性** 直接传递时,CPU 端数组长度必须与 GLSL 声明完全一致 [^2]。 2. **UBO 内存对齐** 使用 `layout(std140)` 确保内存布局匹配,避免数据错位 [^1]。 3. **纹理精度** 纹理传递时需注意浮点精度和纹理尺寸限制。 4. **动态数组支持** OpenGL ES 3.1+ 支持着色器中的不定长数组,需配合缓冲区内存偏移访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ht巷子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值