目前有 4 种方法可以做到这一点:标准 1D 纹理、缓冲区纹理、统一缓冲区和着色器存储缓冲区。
一维纹理
使用此方法,您可以使用glTex(Sub)Image1D 用您的数据填充一维纹理。由于您的数据只是一个浮点数组,因此您的image format 应该是GL_R32F。然后,您可以通过简单的texelFetch 调用在着色器中访问它。 texelFetch 采用纹理坐标(因此得名),并关闭所有过滤。所以你只得到一个纹素。
注意:texelFetch 是 3.0+。如果您想使用之前的 GL 版本,则需要将尺寸传递给着色器并手动归一化纹理坐标。
这里的主要优点是兼容性和紧凑性。这将适用于 GL 2.1 硬件(使用符号)。而且您没有必须使用GL_R32F 格式;你可以使用GL_R16F 半浮点数。或者 GL_R8 如果您的数据对于标准化字节是合理的。大小对整体性能意义重大。
主要缺点是尺寸限制。您仅限于拥有最大纹理大小的一维纹理。在 GL 3.x 级硬件上,这将是 8,192 左右,但保证不少于 4,096。
统一缓冲区对象
它的工作方式是在着色器中声明一个统一块:
layout(std140) uniform MyBlock
{
float myDataArray[size];
};
然后您可以像访问数组一样访问着色器中的数据。
回到 C/C++/etc 代码中,您创建一个缓冲区对象并用浮点数据填充它。然后,您可以将该缓冲区对象与MyBlock 统一块相关联。 More details can be found here.
这种技术的主要优点是速度和语义。速度取决于实现与纹理相比如何处理统一缓冲区。纹理提取是全局内存访问。统一缓冲区访问通常不是;当着色器在渲染中使用时初始化时,统一缓冲区数据通常被加载到着色器中。从那里,它是一个本地访问,速度要快得多。
从语义上讲,这更好,因为它不仅仅是一个平面数组。对于您的特定需求,如果您只需要float[],那没关系。但是如果你有一个更复杂的数据结构,语义可能很重要。例如,考虑一组灯。灯光有位置和颜色。如果您使用纹理,则获取特定灯光的位置和颜色的代码如下所示:
vec4 position = texelFetch(myDataArray, 2*index);
vec4 color = texelFetch(myDataArray, 2*index + 1);
使用统一缓冲区,它看起来就像任何其他统一访问。您已经命名了可以称为position 和color 的成员。所以所有的语义信息都在那里;更容易理解发生了什么。
这也有大小限制。 OpenGL 要求实现为统一块的最大大小提供至少 16,384 字节。这意味着,对于浮点数组,您只能获得 4,096 个元素。再次注意,这是实现所需的最小值;一些硬件可以提供更大的缓冲区。例如,AMD 在其 DX10 级硬件上提供 65,536 个。
缓冲纹理
这些是一种“超级 1D 纹理”。它们有效地允许您access a buffer object from a texture unit。虽然它们是一维的,但它们不是一维纹理。
您只能在 GL 3.0 或更高版本中使用它们。而且您只能通过texelFetch 函数访问它们。
这里的主要优势是尺寸。缓冲区纹理通常非常巨大。虽然规范通常是保守的,要求缓冲区纹理至少为 65,536 字节,但大多数 GL 实现允许它们的大小范围为 mega字节。实际上,通常最大大小受可用 GPU 内存的限制,而不是硬件限制。
此外,缓冲区纹理存储在缓冲区对象中,而不是像 1D 纹理这样更不透明的纹理对象。这意味着您可以使用一些buffer object streaming techniques 来更新它们。
这里的主要缺点是性能,就像一维纹理一样。缓冲纹理可能不会比 1D 纹理慢,但它们也不会像 UBO 一样快。如果你只是从他们身上拉一个浮子,那不应该是一个问题。但是,如果您要从中提取大量数据,请考虑使用 UBO。
着色器存储缓冲区对象
OpenGL 4.3 提供了另一种处理方式:shader storage buffers。它们很像统一缓冲区;您可以使用与统一块几乎相同的语法来指定它们。主要区别在于您可以写信给他们。显然这对您的需求没有用处,但还有其他区别。
从概念上讲,着色器存储缓冲区是缓冲区纹理的另一种形式。因此,着色器存储缓冲区的大小限制比统一缓冲区大很多。最大 UBO 大小的 OpenGL 最小值为 16KB。最大 SSBO 大小的 OpenGL 最小值为 16MB。因此,如果您有硬件,它们是 UBO 的有趣替代品。
请务必将它们声明为 readonly,因为您没有写信给它们。
相对于 UBO,这里的潜在劣势再次是性能。 SSBO 通过缓冲区纹理像image load/store operation 一样工作。基本上,它是围绕imageBuffer 图像类型的(非常好的)语法糖。因此,读取这些数据的速度可能与读取 readonly imageBuffer 的速度相同。
目前尚不清楚通过缓冲区图像通过图像加载/存储读取是否比缓冲区纹理更快或更慢。
另一个潜在问题是您必须遵守non-synchronous memory access 的规则。这些很复杂,很容易让你绊倒。