GLSL - 将纹理的特定颜色更改为另一种颜色

时间:2013-11-15 23:51:09

标签: ios opengl-es glsl

我的纹理由4种不同的颜色组成。我想将每种颜色更改为不同的颜色。我尝试了以下方式:

precision mediump float;

varying lowp vec4 vColor;
varying highp vec2 vUv;

uniform sampler2D texture;

bool inRange( float c1, float c2 ) {
  return abs( c1 - c2 ) < 0.01;
}

void main() {
  vec4 c = texture2D(texture, vUv);

  if ( inRange( c.r, 238.0/255.0 ) && inRange( c.g, 255.0/255.0 ) && inRange( c.b, 84.0/255.0 ) )
    c = vec4( 254.0/255.0, 254.0/255.0, 247.0/255.0, 1.0 );

  else if ( inRange( c.r, 15.0/255.0 ) && inRange( c.g, 59.0/255.0 ) && inRange( c.b, 5.0/255.0 ) )
    c = vec4( 65.0/255.0, 65.0/255.0, 65.0/255.0, 1.0 );

  else if ( inRange( c.r, 157.0/255.0 ) && inRange( c.g, 184.0/255.0 ) && inRange( c.b, 55.0/255.0 ) )
    c = vec4( 254.0/255.0, 247.0/255.0, 192.0/255.0, 1.0 );

  else if ( inRange( c.r, 107.0/255.0 ) && inRange( c.g, 140.0/255.0 ) && inRange( c.b, 38.0/255.0 ) )
    c = vec4( 226.0/255.0, 148.0/255.0, 148.0/255.0, 1.0 );

  gl_FragColor = c;
}

这很有效。但它非常慢。我在iPhone上运行它,但计算不是那么难,或者我错过了什么?

有更快的方法吗?

2 个答案:

答案 0 :(得分:3)

分支对着色器性能不利。通常,GPU一次执行多个片段着色器(每个着色器用于它们自己的片段)。它们全部以锁步方式运行 - SIMD处理意味着实际上所有并行片段处理器运行相同的代码但在不同的数据上运行。当你有条件时,不同的片段可能在不同的代码路径上,所以你失去了SIMD并行性。

此类应用程序的最佳性能技巧之一是使用Color Lookup Table。您提供3D纹理(查找表)并使用GLSL texture3D函数查找它 - 输入坐标是原始颜色的R,G和B值,输出是替换颜色。

即使在移动硬件上也非常快 - 片段着色器不需要进行任何计算,并且通常在片段着色器运行之前缓存纹理查找。

构建查找表纹理很容易。从概念上讲,它是对每个可能的RGB值进行编码的立方体(x轴是从0.0到1.0的R,y轴是G,z轴是B)。如果将其组织为2D图像,则可以在您喜欢的图像编辑器中打开它并应用您喜欢的任何颜色转换过滤器。过滤后的图像是您的转换查找表。对here技术有一个不错的写作,GPU Gems 2有另一个。使用核心图像过滤器应用的该技术的更一般性讨论是in Apple's documentation library

答案 1 :(得分:1)

编辑:提问者证实,存在任何分支导致令人难以置信的减速。我将尝试无分支解决方案。

好吧,如果分支(包括使用三元“?”运算符)不可用,则只能使用算术。

一种可能的解决方案(从维护角度看是可怕的,但可能符合您的需要)是使用多项式将输入颜色映射到输出颜色,这些多项式为您关注的4种颜色提供所需的输出。我分别处理了3个RGB颜色通道,并将输入/输出点插入到具有立方体拟合的wolfram alpha中(例如红色通道的示例:http://www.wolframalpha.com/input/?i=cubic+fit+%7B238.0%2C+254.0%7D%2C%7B15.0%2C+65.0%7D%2C+%7B157.0%2C+254.0%7D%2C+%7B107.0%2C+226.0%7D)。您可以使用任何多项式拟合程序。

红色通道的代码是:

float redResult = 20.6606 + 3.15457 * c.r - 0.0135167 * c.r*c.r + 0.0000184102 c.r*c.r*c.r

使用绿色和蓝色通道冲洗并重复此过程,然后使用着色器。请注意,您可能希望以科学记数法指定非常小的系数以保持准确性...我不知道您的特定驱动程序如何处理浮点文字。

即便如此,你可能(可能)有精确问题,但值得一试。

另一种可能性是使用近似Bump Function(我说是近似的,因为你实际上并不关心平滑约束)。你只想要一个你所关心的颜色为1的值,而另一个远离其他地方的值为0。假设你有一个三组件凹凸功能:bump3,它取一个vec3用于凹凸的位置,一个vec3用于评估函数的位置。然后你可以改写你的第一个条件之一:

  if ( inRange( c.r, 238.0/255.0 ) && inRange( c.g, 255.0/255.0 ) && inRange( c.b, 84.0/255.0 ) )
    c = vec4( 254.0/255.0, 254.0/255.0, 247.0/255.0, 1.0 );

为:

  vec3 colorIn0  = vec3(238.0/255.0, 255.0/255.0, 84.0/255.0);
  vec3 colorOut0 = vec3(254.0/255.0, 254.0/255.0, 247.0/255.0)
  result.rgb = c.rgb + bump3(colorIn0, c.rgb)) * (colorOut0-colorIn0);

如果您的硬件上的最大/最小速度很快(它们可能是引擎盖下的完整分支:(),则可能是快速而脏的bump3()实现:

float bump3(vec3 b, vec3 p) {
   vec3 diff = abs(b-p);
   return max(0.0, 1.0 - 255.0*(diff.x + diff.y + diff.z));
}

bump3的其他可能性可能是滥用smoothstep(再次,如果你的硬件很快)或使用指数。

多项式方法具有增加(偶然)的好处,可以将地图推广到不仅仅是四种颜色,但需要许多算术运算,是维护的噩梦,并且可能会遇到精度问题。另一方面,凹凸函数方法应该产生与当前着色器相同的结果,即使输入不是这四种颜色中的一种,并且更易读和可维护(添加另一种颜色对是微不足道的,与多项式方法)。但是,在我给出的实现中,它使用了max,这可能是引擎盖下的一个分支(我希望不是,geez)。

以下原始答案

知道如何获取时序信息会很好,所以我们可以确定它的这个着色器很慢(你可以通过将它作为一个快速破解的传递着色器来测试它...我推荐使用使用探查器虽然)。这样一个简单的着色器很慢似乎非常奇怪。

否则,如果你的纹理真的只有那4种颜色(并且它是有保证的),那么你可以通过从最后一个分支中删除if来简单地将inRange调用的数量从12减少到3(只需将它变为其他),然后只测试c的r值。我不知道iPhone的glsl优化器是如何工作的,但是你可以进一步尝试用三元运算符替换if语句,看看是否有所作为。这些是我能想到的唯一变化,不幸的是,如果你的纹理不能保证只有那4种颜色,你就无法进行明确的优化。

我想再次指出,在尝试优化之前,你应该确保这个着色器导致速度减慢。

相关问题