将值列表传递给片段着色器

Vil*_*nde 70 opengl glsl

我想将一个值列表发送到片段着色器.它是一个可能很大(几千个项目长)的单精度浮标列表.片段着色器需要随机访问此列表,我想在每个帧上刷新CPU的值.

我正在考虑如何做到这一点:

  1. 作为数组类型的统一变量("uniform float x [10];").但是这里似乎有限制,在我的GPU上发送超过几百个值非常慢,而且当我宁愿在运行时更改它时,我还必须对着色器中的上限进行硬编码.

  2. 作为高度为1且列表宽度的纹理,然后使用glCopyTexSubImage2D刷新数据.

  3. 其他方法?我最近没有跟上GL规范中的所有变化,也许还有一些专门为此目的设计的方法?

Nic*_*las 130

目前有4种方法可以做到这一点:标准1D纹理,缓冲纹理,统一缓冲区和着色器存储缓冲区.

1D纹理

使用此方法,您可以使用glTex(Sub)Image1D数据填充1D纹理.由于您的数据只是一个浮点数组,因此您的图像格式应为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];
};
Run Code Online (Sandbox Code Playgroud)

然后,您可以像使用数组一样在着色器中访问该数据.

回到C/C++/etc代码,您创建一个缓冲区对象并用浮点数据填充它.然后,您可以将该缓冲区对象与MyBlock统一块关联.更多详细信息可以在这里找到.

这种技术的主要优点是速度和语义.速度是由于实现如何处理与纹理相比的统一缓冲区.纹理提取是全局内存访问.统一缓冲区访问通常不是; 当着色器在渲染时被初始化时,统一缓冲区数据通常被加载到着色器中.从那里,它是一个本地访问,这是更快.

从语义上讲,这更好,因为它不仅仅是一个扁平数组.根据您的具体需求,如果您需要的只是一个float[],那无关紧要.但是如果你有一个更复杂的数据结构,语义可能很重要.例如,考虑一组灯光.灯具有位置和颜色.如果使用纹理,则获取特定灯光的位置和颜色的代码如下所示:

vec4 position = texelFetch(myDataArray, 2*index);
vec4 color = texelFetch(myDataArray, 2*index + 1);
Run Code Online (Sandbox Code Playgroud)

使用统一缓冲区,它看起来就像任何其他统一访问.您有一个名为成员,可以调用positioncolor.所以所有的语义信息都在那里; 它更容易理解发生了什么.

这也存在尺寸限制.OpenGL要求实现为统一块的最大大小提供至少16,384字节.这意味着,对于float数组,只能获得4,096个元素.再次注意,这是实现所需的最低要求; 某些硬件可以提供更大的缓冲区.例如,AMD在其DX10级硬件上提供65,536个.

缓冲纹理

这些都是"超级一维纹理".它们有效地允许您从纹理单元访问缓冲区对象.虽然它们是一维的,但它们不是一维纹理.

您只能在GL 3.0或更高版本中使用它们.而且您只能通过该texelFetch功能访问它们.

这里的主要优点是尺寸.缓冲区纹理通常非常巨大.虽然规范通常是保守的,但要求缓冲区纹理至少为65,536字节,大多数GL实现允许它们的大小范围为字节.实际上,通常最大尺寸受可用GPU内存的限制,而不受硬件限制.

此外,缓冲区纹理存储在缓冲区对象中,而不是像1D纹理那样的不透明纹理对象.这意味着您可以使用一些缓冲区对象流技术来更新它们.

这里的主要缺点是性能,就像1D纹理一样.缓冲区纹理可能不会比1D纹理慢,但它们也不会像UBO那样快.如果你只是从它们中拉出一个浮子,那不应该是一个问题.但是如果您从中提取大量数据,请考虑使用UBO.

着色器存储缓冲区对象

OpenGL 4.3提供了另一种处理方式:着色器存储缓冲区.它们很像统一的缓冲区; 使用几乎与均匀块相同的语法指定它们.主要区别在于你可以写信给他们.显然,这对您的需求没有用,但还有其他差异.

从概念上讲,着色器存储缓冲区是缓冲纹理的替代形式.因此,着色器存储缓冲区的大小限制是很多比均匀的缓冲器大.最大UBO大小的OpenGL最小值为16KB.最大SSBO大小的OpenGL最小值为16MB.因此,如果您拥有硬件,它们是UBO的有趣替代品.

请务必将它们声明为readonly,因为您没有写信给他们.

相对于UBO而言,这里的潜在劣势是表现.SSBO 通过缓冲区纹理像图像加载/存储操作一样工作.基本上,它是imageBuffer图像类型周围的(非常好的)语法糖.因此,从这些读取将可能以从a的读取速度执行readonly imageBuffer.

是否通过缓冲区图像通过图像加载/存储读取比缓冲区纹理更快或更慢还不清楚.

另一个潜在的问题是您必须遵守非同步内存访问的规则.这些很复杂,很容易让你失望.

  • 这是一个很棒的答案,谢谢.我需要花费数小时的谷歌搜索才能在其他地方找到所有这些信息. (9认同)
  • @ChristianRau:当然.GPU有很多内存缓冲区,其中一些相当大.由于它是一个统一的(因此大小固定),每个都在最多4个独立的线程之间共享.当你_change_使用你正在使用的程序或统一缓冲区时,你只需要上传它们.因此,结束一个顶点/片段并开始一个新顶点/片段不需要更改它.对于片段着色器繁重的过程,即使有30个SIMD,您也只能复制20次.无论渲染多少片段. (3认同)

Chr*_*ica 7

这听起来像纹理缓冲区对象的一个很好的用例.这些与常规纹理没有多大关系,基本上允许您在着色器中以简单的线性数组的形式访问缓冲区对象的内存.它们类似于1D纹理,但不会被过滤,只能通过整数索引访问,这听起来就像您将其称为值列表时需要执行的操作.它们还支持比1D纹理更大的尺寸.要更新它,你可以再使用标准缓冲对象的方法(glBufferData,glMapBuffer,...).

但另一方面,我认为它们需要使用GL3/DX10硬件,甚至已经成为OpenGL 3.1的核心.如果您的硬件/驱动程序不支持它,那么您的第二个解决方案将是选择的方法,而是使用1D纹理而不是宽度x 1 2D纹理).在这种情况下,您还可以使用非平面2D纹理和一些索引魔法来支持大于最大纹理大小的列表.

但我认为,纹理缓冲是您问题的完美匹配.要获得更准确的见解,您还可以查看相应的扩展规范.

编辑:为了回应Nicol关于统一缓冲对象的评论,您还可以在这里查看两者的一些比较.我仍然倾向于TBO,但不能说明原因,只是因为我认为它更符合概念.但也许Nicol可以提供一些更深入了解此事的人.


edv*_*dig 6

一种方法是使用像你提到的统一数组.另一种方法是使用一维"纹理".查找GL_TEXTURE_1D和glTexImage1D.我个人更喜欢这种方式,因为你不需要如你所说的那样在着色器代码中硬编码数组的大小,而且opengl已经具有用于在GPU上上传/访问1D数据的内置函数.