使用LUT加速移动设备的Trig重型着色器


10

我正在尝试使此着色器在非常老的iDevice以及最终的Android上运行。

即使将每个片段的代码减少到2个正弦函数,着色器也以约20fps的速度运行。

我已经考虑过从旧的着色技术书中摘下一片叶子,并创建一个包含一堆预定义触发值的数组,并以某种方式使用这些值来近似着色器。

在上面链接的着色器中,我已经在模拟,如果四舍五入发送到trig函数的值,则鼠标越靠左(向下),着色器的质量越差。它实际上非常酷,因为它确实在左侧非常像一个完全不同且非常酷的着色器。

无论如何,我有两个难题:

  1. 我不知道在GLSL着色器中恒定或统一的数组中拥有360个值的最有效方法是什么?
  2. 我想不通如何像通常要在0到360之间的角度放置数字一样(是的,我知道GPU使用弧度),我会这样做。

    func range(float angle)
    {
       float temp = angle
       while (temp > 360) {temp -= 360;}
       while (temp < 0)   {temp += 360;}
       return temp;
    }
    

    但是,GLSL不允许使用while循环或递归函数。


我不知道实施此方法是否可行,但是将预先计算出的正弦值分布不均匀会有所帮助,在正弦曲线的斜率最陡的情况下,聚集更紧密的值,而在正弦曲线的斜率最大时,较少的值变化不大?这是否可以在需要的地方提供更高的准确性,而无需存储大量值?
trichoplax

5
关于#2,内置mod功能是您想要的。你会写mod(angle, 360.0)
内森·里德

1
@trichoplax是个绝妙的主意,但我不知道那时您将如何查找表中的值。假设我们将它们放在一个数组中,其中一些更加集中。我们如何找到正确的索引?
J.Doe '16

6
如何将您的值放入3通道1D纹理中?这样一来,您就可以通过单一纹理查找的价格获得罪恶,cos和晒黑。如果将0-2pi角度映射到0-1 UV并使用重复纹理模式,则您甚至不需要mod调用,它将自动“包装”,并且您还可以在存储的值之间获得线性滤波的近似值,而不是捕捉到最近的一个。
鲁斯

3
很多时候,通过不使用角度而是以sin / cos对开始和结束,并为半角度等使用trig身份,可以消除用于几何图形的trig函数。
棘轮怪胎

Answers:


8

有人在评论中反复提出过这一建议,但是没有人感到需要给出正确的答案,因此,为了完整起见,解决此问题的简单直接的通用解决方案可能是使用纹理作为查找表,特别是一维纹理包含函数可能输入范围内的所有值(即 /)。这具有各种优点:[ 0 2 π [0,360)[0,2π)

  • 它使用归一化坐标,即,您可以通过将角度从映射到来访问纹理。这意味着您的着色器实际上不必关心特定值的数量。您可以根据所需的内存/速度与质量的权衡来调整其大小(特别是在较旧/嵌入式硬件上,无论如何,纹理大小都可能需要2的幂)。[ 0 1 ][0,360][0,1]
  • 您将获得不必进行类似循环的间隔调整的额外好处(尽管无论如何您都不需要循环,而可以使用模运算)。只是GL_REPEAT用作纹理的包装模式,当使用参数> 1(对于负参数)进行访问时,它将自动从头开始
  • 而且,您还可以通过使用纹理过滤器来基本上免费(或说几乎免费)在数组中的两个值之间线性插值GL_LINEAR,从而获得甚至没有存储的值。当然,对于三角函数而言,线性插值并不是100%准确的,但肯定比没有插值要好。
  • 您可以使用RGBA纹理(或所需的许多组件)在纹理中存储多个值。这样,您可以通过单个纹理查找获得例如sin和cos。
  • 对于sin和cos,无论如何您只需要将值存储在,就可以自然地从常见8位定点格式的规范化范围进行频。但是,这可能不足以满足您的需求。有人建议使用16位浮点值,因为它们比通常的8位归一化定点值更精确,但比真正的32位浮点值占用的内存少。但是话又说回来,我也不知道您的实现是否一开始就支持浮点纹理。如果不是,那么也许您可以使用2个8位定点分量,并将它们组合成具有类似值的单个值(或什至更多分量以获得更细的颗粒)。这使您可以再次从多组分纹理中受益。[ 0 1 ][1,1][0,1]float sin = 2.0 * (texValue.r + texValue.g / 256.0) - 1.0;

当然,这是否是一个更好的解决方案,仍然必须进行评估,因为纹理访问也不是完全免费的,以及纹理大小和格式的最佳组合是什么。

至于用数据填充纹理并处理您的评论之一,您必须考虑纹理过滤会在纹理像素中心返回精确值,即纹理坐标偏离纹理像素大小的一半。因此,是的,您应该.5texels 处生成值,即在应用程序代码中类似于以下内容:

float texels[256];
for(unsigned int i = 0; i < 256; ++i)
    texels[i] = sin((i + .5f) / 256.f) * TWO_PI);
glTexImage1D(GL_TEXTURE_1D, 0, ..., 256, 0, GL_RED, GL_FLOAT, texels);

但是,您可能仍然希望将此方法的性能与使用小型统一数组的方法(即uniform float sinTable[361],或者实际上更少,请注意实现对统一数组大小的限制)进行比较,然后使用以下方法分别加载相应的值:glUniform1fv并使用该功能将角度调整为并将其舍入到最接近的值:[0,360)mod

angle = mod(angle, 360.0);
float value = sinTable[int(((angle < 0.0) ? (angle + 360.0) : angle) + 0.5)];

1
这是将查询表存储在纹理中的有趣扩展。它(ab)使用N线性纹理插值来获得数据点,表面,体积和超体积的高阶插值(即优于线性插值)。blog.demofox.org/2016/02/22/…–
艾伦·沃尔夫
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.