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


74

我想将值列表发送到片段着色器中。它可能是单精度浮点数很大(两千个项目长)的列表。片段着色器需要对该列表的随机访问,我想刷新每个帧上CPU的值。

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

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

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

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


1
我不是GLSL的专家,但是出于好奇,为什么/如何为着色器使用数千个参数?
2011年

5
@GeorgeProfenza我不会说它是成千上万个单独的参数,而是一个包含值表的单个参数。着色器将在此列表中查找一个值,其中索引取决于gl_FragCoord和其他因素。
维尔·克鲁姆林德

Answers:


139

当前有4种方法可以执行此操作:标准1D纹理,缓冲区纹理,统一缓冲区和着色器存储缓冲区。

一维纹理

使用此方法,您可以glTex(Sub)Image1D用数据填充一维纹理。由于您的数据只是浮点数的数组,因此您的图片格式应为GL_R32F。然后,您可以通过简单的texelFetch调用在着色器中对其进行访问。texelFetch获取texel坐标(因此命名),并关闭所有过滤。因此,您得到的正是一个纹理像素。

注意:texelFetch是3.0+。如果要使用以前的GL版本,则需要将尺寸传递给着色器并手动规范化纹理坐标。

这里的主要优点是兼容性和紧凑性。这将在GL 2.1硬件上运行(使用表示法)。而你不具备在使用GL_R32F格式; 您可以使用GL_R16F半浮点数。或者,GL_R8如果您的数据对于标准化字节是合理的。尺寸对于整体性能可能意义重大。

主要缺点是尺寸限制。您只能使用最大纹理大小的一维纹理。在GL 3.x级硬件上,此值约为8,192,但保证不低于4,096。

统一缓冲区对象

这种工作方式是在着色器中声明一个统一块:

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

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

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

从语义上讲,这是更好的方法,因为它不仅仅是平面数组。对于您的特定需求,如果您只需要一个float[],就没有关系了。但是,如果您具有更复杂的数据结构,则语义可能很重要。例如,考虑一个灯光阵列。灯光具有位置和颜色。如果使用纹理,则用于获取特定光源的位置和颜色的代码如下所示:

使用统一缓冲区,它看起来就像其他统一访问一样。您已经命名了可以称为position和的成员color。这样所有的语义信息就在那里。更容易了解正在发生的事情。

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

缓冲区纹理

这些是一种“超级1D纹理”。它们有效地允许您从纹理单元访问缓冲区对象。尽管它们是一维的,但它们不是一维纹理。

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

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

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

就像1D纹理一样,这里的主要缺点是性能。缓冲区纹理可能不会比一维纹理慢,但它们也不会像UBO一样快。如果您只是从它们中拉出一个浮标,则不必担心。但是,如果您要从中提取大量数据,请考虑使用UBO。

着色器存储缓冲区对象

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

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

请确保将它们声明为readonly,因为您没有在写信给他们。

相对于UBO,此处的潜在缺点再次是性能。SSBO通过缓冲区纹理像图像加载/存储操作一样工作。基本上,它是imageBuffer图像类型周围的(非常好)语法糖。因此,从这些读取可能会以从读取的速度执行readonly imageBuffer

目前尚不清楚通过图像加载/通过缓冲图像存储读取是否比缓冲纹理更快或更慢。

另一个潜在的问题是,您必须遵守非同步内存访问的规则。这些很复杂,很容易使您绊倒。


1
也许我对GPU架构没有太多的了解,但是“ UBO不是全局内存,初始化时会加载到着色器中”这样的语句真的适合至少65k字节的大小吗?
Christian Rau

3
@ChristianRau:好的。GPU有很多内存缓冲区,其中一些缓冲区很大。而且由于它是统一的(因此大小固定),因此每个线程最多可在4个单独的线程之间共享。而且,仅当您更改正在使用的程序或统一缓冲区时,才需要上传它们。因此,结束一个顶点/片段并开始一个新的顶点/片段不需要更改它。对于碎片着色器繁重的过程,即使有30个SIMD,也只能进行20次复制。无论渲染多少片段。
Nicol Bolas

9
谢谢,这是一个很棒的答案。我将需要花费大量时间进行搜寻,才能在其他地方找到所有这些信息。
Ville Krumlinde

这非常有用,最后每个人都需要有关数据问题的概述。有很多有趣的限制。普通纹理受到限制,可能是由于在图形卡上使用了纹理单元所致。
Robetto 2015年

8

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

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

但是,我认为纹理缓冲区是您问题的完美选择。为了获得更准确的了解,您可能还会研究相应的扩展规范

编辑:为了回应Nicol关于统一缓冲区对象的评论,您还可以在这里查找两者的一些比较。我仍然倾向于使用TBO,但不能真正理由说明原因,只是因为我认为它在概念上更合适。但是,也许尼科尔可以提供答案,使人们对这件事有更多的了解。


谢谢,这看起来可能正是我想要的。我的AMD GPU支持此扩展,现在我只需要估计它在用户群中的普及程度即可
Ville Krumlinde 2011年

@VilleKrumlinde任何GL3 / DX10硬件都应该支持它,至少在硬件方面。
Christian Rau

您也可以使用统一缓冲区。它们通常不能和纹理缓冲区一样大,但是在着色器中访问和使用它们要容易得多。而且内存访问速度也更快。
Nicol Bolas

@NicolBolas在哪条路是他们更容易获得和使用的着色器(除substituingtexture......[...])?他们真的比TBO快吗?一些见解会很不错,因为我对这两者都不了解。也许您可以添加一个答案,以解释UBO替代方案?
克里斯蒂安·劳

6

一种方法是使用您提到的统一数组。另一种方法是使用一维“纹理”。查找GL_TEXTURE_1D和glTexImage1D。我个人更喜欢这种方式,因为您不需要像您所说的那样在着色器代码中对阵列的大小进行硬编码,并且opengl已经具有内置功能,可以在GPU上上载/访问1D数据。


2

我想说的可能不是数字1。您的着色器统一寄存器数量有限,具体取决于卡。您可以查询GL_MAX_FRAGMENT_UNIFORM_COMPONENTS以了解您的限额。在较新的卡上,它可以运行到成千上万个,例如,Quadro FX 5500显然具有2048。(http://www.nvnews.net/vbulletin/showthread.php?t=85925)。这取决于您希望它在什么硬件上运行,以及可能还要发送给着色器的其他制服。

根据您的要求,可以使2号正常工作。很抱歉,这里的含糊不清,希望其他人可以给您一个更准确的答案,但是您必须明确说明在旧的着色器模型卡中调用了多少个纹理。这也取决于每个片段要读取多少个纹理,您可能不想再次尝试每个片段读取1000个元素,具体取决于着色器模型和性能要求。您可以将值打包到纹理的RGBA中,每个纹理调用可以读取4次,但是由于需要随机访问,这可能对您没有帮助。

我不确定第3位,但我建议也许看看UAV(无序访问视图),尽管我认为这仅是DirectX,没有等效的openGL。我认为openGL有n​​Vidia扩展,但是您再次将自己限制在非常严格的最低规格。

将1000的数据项传递到片段着色器不太可能是解决问题的最佳解决方案。.如果您提供了有关要实现的目标的更多详细信息,则可能会得到替代建议?


谢谢。我可能会对每个片段从此数组进行一次读取。它是影响最终片段值的值列表,当我以这种方式考虑时,也许一维纹理是自然的选择。唯一的问题是,我宁愿使用精确的整数查找而不是浮点纹理坐标。
维尔·克鲁姆林德

对于每个片段一次读取,听起来确实是一种行之有效的方法。只要您在1D纹理上使用最近的过滤,您将始终收到与您在纹理中输入的值匹配的值(当然会受到浮点数和纹理格式精度误差的影响)
混合

1
@VilleKrumlinde如果要使用整数索引进行纯数组访问,则应使用TBO(至少在GL3 / DX10硬件上)。看我的回答。
克里斯汀·劳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.