将值列表传递给片段着色器
我想发送一个值列表到片段着色器。 这是一个可能很大(几千项长)的单精度浮标清单。 片段着色器需要随机访问这个列表,我想在每一帧刷新CPU的值。
我正在考虑如何做到这一点的选项:
-
作为数组types的统一variables(“uniform float x [10];”)。 但是这里似乎是有限制的,在我的GPU上发送超过几百个数值是非常慢的,而且当我想要在运行时改变它时,我也必须对着色器中的上限进行硬编码。
-
作为具有高度1和我的列表宽度的纹理,然后使用glCopyTexSubImage2D刷新数据。
-
其他方法? 我最近并没有跟上GL规范的所有变化,或许还有其他一些专门为此目的而devise的方法?
目前有4种方法可以做到这一点:标准1D纹理,缓冲区纹理,统一缓冲区和着色器存储缓冲区。
1D纹理
使用这种方法,您可以使用glTex(Sub)Image1D
来填充数据的一维纹理。 由于你的数据只是一个浮点数组,你的图像格式应该是GL_R32F
。 然后用简单的texelFetch
调用在着色器中访问它。 texelFetch
采用texel坐标(因此名称),并closures所有过滤。 所以你得到一个texel。
注意: texelFetch
是3.0+。 如果您想使用之前的GL版本,则需要将大小传递给着色器,并手动规范纹理坐标。
这里的主要优点是兼容性和紧凑性。 这将在GL 2.1硬件上使用(使用符号)。 而且你不必使用GL_R32F
格式。 你可以使用GL_R16F
半浮点数。 或者GL_R8
如果你的数据对于规范化的字节是合理的。 大小对于整体性能可能意味着很多。
主要缺点是尺寸限制。 您仅限于拥有最大纹理尺寸的一维纹理。 在GL 3.x级的硬件上,这将是大约8,192,但是保证不less于4096。
统一的缓冲区对象
这个工作的方式是你在着色器中声明一个统一的块:
layout(std140) uniform MyBlock { float myDataArray[size]; };
然后,就像数组一样访问着色器中的数据。
回到C / C ++ / etc代码中,您将创build一个缓冲区对象并用浮点数据填充它。 然后,您可以将该缓冲区对象与MyBlock
统一块相关联。 更多详细信息可以在这里find。
这种技术的主要优点是速度和语义。 速度是由于实现如何处理统一缓冲区与纹理相比。 纹理提取是全局内存访问。 统一缓冲区访问通常不是; 统一的缓冲区数据通常在着色器在渲染时被初始化时加载到着色器中。 从那里,这是一个本地访问,这是更快。
在语义上,这是更好的,因为它不只是一个平面arrays。 对于你的具体需求,如果你所需要的只是一个float[]
,那并不重要。 但是,如果你有一个更复杂的数据结构,语义可能是重要的。 例如,考虑一些灯光。 灯具有一个位置和一个颜色。 如果你使用一个纹理,你的代码来获得一个特定的光的位置和颜色如下所示:
vec4 position = texelFetch(myDataArray, 2*index); vec4 color = texelFetch(myDataArray, 2*index + 1);
使用统一的缓冲区,它看起来就像任何其他统一的访问。 你有名字的成员,可以被称为position
和color
。 所以所有的语义信息都在那里。 很容易理解发生了什么事情。
这也有大小限制。 OpenGL要求实现为统一块的最大尺寸提供至less16,384个字节。 这意味着,对于浮点数组,你只能得到4,096个元素。 再次注意,这是实现所需的最小值 ; 一些硬件可以提供更大的缓冲区。 例如,AMD在他们的DX10级硬件上提供了65536个。
缓冲区纹理
这是一种“超级一维纹理”。 它们实际上允许您从纹理单元访问缓冲区对象 。 尽pipe它们是一维的,但它们不是一维的纹理。
您只能从GL 3.0或更高版本使用它们。 你只能通过texelFetch
函数访问它们。
这里的主要优势是规模。 缓冲贴图通常可能相当庞大。 虽然规范通常是保守的,但要求缓冲区纹理至less有65,536字节,大多数GL实现允许它们的大小在兆字节范围内。 实际上,通常最大尺寸受限于可用的GPU内存,而不是硬件限制。
此外,缓冲区纹理存储在缓冲区对象中,而不是像一维纹理那样更不透明的纹理对象。 这意味着你可以使用一些缓冲对象stream技术来更新它们。
这里的主要缺点是性能,就像一维纹理一样。 缓冲纹理可能不会比1D纹理慢,但它们不会像UBO那么快。 如果你只是从他们那里拉出一个浮动,这应该不是一个问题。 但是,如果您从中抽取大量数据,请考虑使用UBO。
着色器存储缓冲区对象
OpenGL 4.3提供了另一种处理方法: 着色器存储缓冲区 。 他们很像统一的缓冲区; 您可以使用与统一块几乎相同的语法来指定它们。 原则上的区别是你可以给他们写信。 显然这对您的需求没有帮助,但还有其他的不同之处。
从概念上讲,着色器存储缓冲区是缓冲纹理的替代forms。 因此,着色器存储缓冲区的大小限制比统一缓冲区大很多 。 最大UBO大小的OpenGL最小值为16KB。 OpenGL最小的最大SSBO大小是16MB 。 所以如果你有硬件,它们是UBO的一个有趣的select。
只要确保把它们声明为readonly
,因为你没有写信给它们。
与UBO相比,这里的潜在缺点是性能再次提高。 SSBO通过caching纹理像图像加载/存储操作一样工作。 基本上,在imageBuffer
图像types周围是(非常好的)语法糖。 因此,这些读取操作可能会以读取readonly imageBuffer
的速度执行。
不pipe是通过图像加载/存储通过缓冲区图像读取比缓冲区纹理更快还是更慢,目前还不清楚。
另一个潜在的问题是您必须遵守非同步内存访问的规则。 这些很复杂,可以很容易地把你绊倒。
这听起来像是纹理缓冲区对象的一个很好的用例。 这些与常规纹理没有多大关系,基本上允许你在一个着色器中以一个简单的线性数组的forms访问一个缓冲区对象的内存。 它们与1D纹理类似,但不会被过滤,只能通过整数索引来访问,这听起来就像您将其称为值列表时所要做的一样。 而且他们也支持比1D纹理更大的尺寸。 为了更新它,你可以使用标准缓冲区对象方法( glBufferData
, glMapBuffer
,…)。
但另一方面,他们需要使用GL3 / DX10硬件,甚至在OpenGL 3.1中做了核心,我想。 如果您的硬件/驱动程序不支持它,那么您的第二个解决scheme将是select的方法,而是使用一维纹理比宽度x 1 2D纹理)。 在这种情况下,您还可以使用非平坦的2D纹理和一些索引魔术来支持大于最大纹理大小的列表。
但是,我认为,纹理缓冲区是您的问题的完美搭配。 为了更准确地了解您可能还需要查看相应的扩展规范 。
编辑:为了回应Nicol关于统一缓冲区对象的评论,你也可以在这里看看这两个比较。 我仍然倾向于TBO,但是不能真正理解为什么,只是因为我认为它在概念上更合适。 但也许尼科尔可以提供更多的洞察力的问题。
一种方法是像你提到的那样使用统一的数组。 另一种方法是使用一维“纹理”。 寻找GL_TEXTURE_1D和glTexImage1D。 我个人比较喜欢这种方式,因为你不需要像着色器那样在着色器代码中硬编码数组的大小,而且opengl已经具有用于在GPU上上传/访问1D数据的内置函数。
我可能会说不是数字1 ..你有着色器的统一登记数量有限,因卡而异。 您可以查询GL_MAX_FRAGMENT_UNIFORM_COMPONENTS以找出您的限制。 在较新的卡上运行成千上万,例如Quadro FX 5500有2048,显然。 (http://www.nvnews.net/vbulletin/showthread.php?t=85925)。; 这取决于你想要运行的硬件,还有其他你可能想要发送给着色器的制服。
2号可以根据您的要求进行工作。 对不起,在这里模糊,希望别人可以给你一个更准确的答案,但你必须明确你在老的着色器模型卡做了多less纹理调用。 这也取决于你想要为每个片段做多less纹理读取,你可能不希望试图读取每个片段的1000个元素,这取决于你的着色器模型和性能要求。 您可以将值包装到纹理的RGBA中,每个纹理调用给出4次读取,但随着访问的需要,这可能无法帮助您。
我不确定3号,但我build议也许看UAV(无序的访问视图),虽然我认为这只是DirectX,没有体面的OpenGL的等效。 我认为openGL有一个nVidia的扩展,但是你再一次限制自己到一个非常严格的最低规格。
将1000个数据项传递给你的片段着色器是不太可能的,这是你的问题的最佳解决scheme..也许如果你提供了更多关于你想要实现的细节,你可能会得到另外的build议?