type
status
date
slug
summary
tags
category
icon
password
💡
uniform变量是一种从CPU向GPU发送数据的方式,常用于传递变换矩阵、光照参数、材质属性等。uniform变量可以在顶点着色器、片元着色器等着色器程序中使用,但它们在着色器执行期间保持不变。我们以OpenGL作为例子,其他API也是类似。

Uniform Buffer Objects

在之前的OpenGL版本中,向着色器传输uniform变量通常需要依赖于glUniformXX系列函数。这些函数允许为着色器中的每个单独的uniform变量设置值,这种方法在处理少量uniform变量时效果很好。然而,当着色器使用大量uniform变量,或者当需要在多个着色器程序之间共享相同的uniform数据时,这种方法就会变得低效和繁琐。
为了解决这些问题,并提高大量uniform数据的管理和传输效率,OpenGL引入了Uniform Buffer Objects(UBOs)。UBOs允许多个uniform变量存储在一个单独的缓冲对象中,这样就可以一次性更新所有变量,并在多个着色器程序之间共享相同的uniform缓冲数据。这不仅减少了API调用的数量,还使得数据管理更加集中和高效。
notion image

使用UBOs的步骤

  1. 定义Uniform块:在GLSL着色器中,定义一个uniform块来指定哪些uniform变量将被存储在UBO中。
    1. 创建UBO:在OpenGL应用程序中,创建一个缓冲对象并将其绑定到GL_UNIFORM_BUFFER目标。
      1. 填充UBO数据:使用glBufferSubData或其他相关函数更新UBO中的数据。
        1. 绑定UBO到Uniform块:需要获取uniform块的索引,并使用glBindBufferBaseglBindBufferRange将UBO绑定到这个块上。

          简化

          GL4.2的时候,在Shader引入了binding的关键字,所以无需要获取uniform块的索引了:
          在主程序,只需要这样创建ubo并绑定:

          优势

          • 性能提升:通过减少API调用和允许更有效的数据更新,UBOs可以提升渲染性能。
          • 数据共享:UBOs使得在多个着色器程序之间共享uniform数据变得简单,无需为每个程序单独设置uniform值。
          • 更好的组织:将相关的uniform变量组织到一个块中可以使着色器代码更加清晰和易于管理。
          UBOs是现代OpenGL程序中管理和传输uniform数据的强大工具,特别是在涉及复杂渲染任务和大量着色器数据时。正确使用UBOs可以显著提高数据传输效率和渲染性能。

          内存布局

          在OpenGL的GLSL(OpenGL Shading Language)中,当使用Uniform Buffer Objects(UBOs)定义uniform块时,可以通过布局限定符来控制块中变量的内存布局。std140packed、和shared是三种可用的布局限定符,它们各自定义了不同的内存布局和对齐规则。了解这些规则对于确保着色器正确读取uniform块中的数据至关重要。

          std140布局

          std140布局提供了一组严格的对齐规则,确保uniform块的内存布局在不同的硬件和平台上保持一致。以下是std140的一些主要对齐规则,假设
          基本对齐量:Base alignment 对齐偏移量:Aligned offset
          数据类型
          对齐规则
          标量(如int和bool)
          每个标量的基本对齐量为N
          向量
          基本对齐量为2N或者4N,这意味着vec3的基本对齐量为4N(即16字节,也就是3个float后多了一个空的4字节位置)
          标量或向量数组
          每个元素的基本对齐量与 vec4 的基本对齐量相同
          矩阵
          存储为大型列向量数组,其中每个向量的基本对齐量为vec4
          结构体
          等于根据前面的规则计算出的其元素的大小,但填充为vec4大小的倍数

          packed布局

          packed布局允许GLSL编译器更自由地打包uniform块中的变量,没有std140那样严格的对齐要求。这可以导致更紧凑的内存布局,减少内存空间的浪费。然而,packed布局的具体行为依赖于编译器和硬件,因此可能在不同的平台和驱动程序之间变得不一致。
          • 自由对齐:在packed布局中,编译器可以自由决定如何对齐块中的变量,可能导致在不同的硬件或驱动程序上有不同的内存布局。
          • 兼容性问题:由于缺乏标准化的对齐规则,使用packed布局可能会导致跨平台兼容性问题。

          shared布局

          shared布局提供了一种介于std140packed之间的布局选项。它允许编译器有一定程度的自由来决定如何对齐和打包uniform块中的变量,但通常会遵循硬件的最佳实践来优化内存布局。
          • 灵活对齐shared布局中的对齐规则比std140更灵活,但比packed更受控制,编译器会尝试找到一个平衡点以优化内存布局。
          • 硬件优化shared布局的具体对齐规则可能因硬件和驱动的不同而有所不同,因此不保证跨平台的一致性。

          结论

          • std140:适合需要确保跨平台一致性和可预测性的场景,但可能会牺牲一些内存效率。
          • packed:可能提高内存效率,减少未使用空间,但在不同的平台和驱动程序之间可能会导致不一致的问题。
          • shared:试图在优化内存使用和保持跨平台一致性之间找到平衡点,适合那些需要一定程度优化但又不想牺牲太多一致性的场景。
          在实际应用中,选择哪种布局取决于内存效率、跨平台一致性和兼容性的具体需求。通常,std140是最常用的选项,因为它提供了最好的一致性保证。
          3D Gaussian Splatting重返茶溪谷
          • Twikoo