简体   繁体   English

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

[英]GLSL - Change specific Color of Texture to another Color

My textures consist of 4 different colors. 我的纹理由4种不同的颜色组成。 I want to change each color to a different color. 我想将每种颜色更改为不同的颜色。 I tried it the following way: 我尝试了以下方式:

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;
}

This works. 这有效。 But it's terribly slow. 但它非常慢。 I'm running this on an iPhone, but the calculations aren't that hard or am I missing something? 我在iPhone上运行它,但计算不是那么难,或者我错过了什么?

Is there a faster way to do this? 有更快的方法吗?

Branches are bad for shader performance. 分支对着色器性能不利。 Normally, the GPU executes multiple fragment shaders (each for their own fragment) at once. 通常,GPU一次执行多个片段着色器(每个着色器用于它们自己的片段)。 They all run in lockstep -- SIMD processing means that in effect all parallel fragment processors are running the same code but operating on different data. 它们全部以锁步方式运行 - SIMD处理意味着实际上所有并行片段处理器运行相同的代码但在不同的数据上运行。 When you have conditionals, it's possible for different fragments to be on different code paths, so you lose SIMD parallelism. 当您有条件时,不同的片段可能位于不同的代码路径上,因此您将失去SIMD并行性。

One of the best performance tricks for this sort of application is using a Color Lookup Table . 这种应用程序的最佳性能技巧之一是使用颜色查找表 You provide a 3D texture (the lookup table) and use the GLSL texture3D function to look up into it -- the input coordinates are the R, G, and B values of your original color, and the output is the replacement color. 您提供3D纹理(查找表)并使用GLSL texture3D函数查找它 - 输入坐标是原始颜色的R,G和B值,输出是替换颜色。

This is very fast, even on mobile hardware -- the fragment shader doesn't have to do any computation, and the texture lookup is usually cached before the fragment shader even runs. 即使在移动硬件上,这也非常快 - 片段着色器不需要进行任何计算,并且通常在片段着色器运行之前缓存纹理查找。

Constructing a lookup table texture is easy. 构建查找表纹理很容易。 Conceptually, it's cube that encodes every possible RGB value (x axis is R from 0.0 to 1.0, y axis is G, z axis is B). 从概念上讲,它是对每个可能的RGB值进行编码的立方体(x轴是从0.0到1.0的R,y轴是G,z轴是B)。 If you organize it as a 2D image, you can then open it in your favorite image editor and apply any color transformation filters you like to it. 如果将其组织为2D图像,则可以在您喜欢的图像编辑器中打开它并应用您喜欢的任何颜色转换过滤器。 The filtered image is your conversion lookup table. 过滤后的图像是您的转换查找表。 There's a decent writeup on the technique here and another in GPU Gems 2 . 有一个在技术,一个体面的书面记录在这里 ,另一个在GPU精粹2 A more general discussion of the technique, applied using Core Image filters, is in Apple's documentation library . 使用Core Image过滤器应用该技术的更一般性讨论在Apple的文档库中

EDIT: It was confirmed by the asker that it is the presence of any branches that causes the incredible slowdown. 编辑:提问者证实,存在任何分支导致令人难以置信的减速。 I will provide an attempt at a branchless solution. 我将尝试无分支解决方案。

Well, if branches (including using the ternary "?" operator) are unusable, you can only use arithmetic. 好吧,如果分支(包括使用三元“?”运算符)不可用,则只能使用算术。

A possible solution (which is hideous from a maintenance perspective, but might fit your need) is to map your input color to output color using polynomials that give desired output for the 4 colors you care about. 一个可能的解决方案(从维护角度看是可怕的,但可能符合您的需要)是使用多项式将输入颜色映射到输出颜色,这些多项式为您关心的4种颜色提供所需的输出。 I treated the 3 RGB color channels separately and plugged in the input/output points into wolfram alpha with a cubic fit (example for the red channel here: 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 ). 我分别处理了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 )。 You could use any polynomial fit program for this purpose. 您可以使用任何多项式拟合程序来实现此目的。

The code for the red channel is then: 红色通道的代码是:

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

Rinse and repeat the process with the green and blue color channels and you have your shader. 使用绿色和蓝色通道冲洗并重复此过程,然后使用着色器。 Note that you may want to specify the very small coefficients in scientific notation to retain accuracy... I don't know how your particular driver handles floating-point literals. 请注意,您可能希望以科学记数法指定非常小的系数以保持准确性...我不知道您的特定驱动程序如何处理浮点文字。

Even then you may (probably) have precision issues, but its worth a shot. 即便如此,你可能(可能)有精确问题,但值得一试。

Another possibility is using an approximate Bump Function (I say approximate, since you don't actually care about the smoothness constraints). 另一种可能性是使用近似的凹凸函数 (我说是近似的,因为你实际上并不关心平滑约束)。 You just want a value thats 1 at the color you care about and 0 everywhere else far enough away. 你只想要一个你所关心的颜色为1的值,而另一个远离其他地方的值为0。 Say you have a three-component bump function: bump3 that takes a vec3 for the location of the bump and a vec3 for the location to evaluate the function at. 假设你有一个三组件凹凸功能:bump3,它取一个vec3用于凹凸的位置,一个vec3用于评估函数的位置。 Then you can rewrite one of your first conditional from: 然后你可以改写你的第一个条件之一:

  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 );

to: 至:

  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);

If max/min are fast on your hardware (they might be full branches under the hood :( ), a possible quick and dirty bump3() implementation might be: 如果您的硬件上的最大/最小速度很快(它们可能是引擎盖下的完整分支:(),则可能是快速且脏的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));
}

Other possibilities for bump3 might be abusing smoothstep (again, if is fast on your hardware) or using the exponential. bump3的其他可能性可能是滥用smoothstep(再次,如果在你的硬件上很快)或使用指数。

The polynomial approach has the added (incidental) benefit of generalizing your map to more than just the four colors, but requires many arithmetic operations, is a maintenance nightmare, and likely suffers from precision issues. 多项式方法具有附加(偶然)的好处,可以将地图推广到不仅仅是四种颜色,但需要许多算术运算,是维护的噩梦,并且可能会遇到精度问题。 The bump function approach, on the other hand, should produce the same results as your current shader, even on input that is not one of those four colors, and is much more readable and maintainable (adding another color pair is trivial, compared to the polynomial approach). 另一方面,凹凸函数方法应该产生与当前着色器相同的结果,即使输入不是这四种颜色中的一种,并且更易读和可维护(添加另一种颜色对是微不足道的,与多项式方法)。 However, in the implementation I gave, it uses a max, which might be a branch under the hood (I hope not, geez). 但是,在我给出的实现中,它使用了max,这可能是引擎盖下的一个分支(我希望不是,geez)。

Original answer below 原答案如下

It would be good to know how you are getting timing information so we can be sure its this shader thats slow (you could test this by just making this a pass-through shader as a quick hack... I recommend getting used to using a profiler though). 知道如何获取时序信息会很好,所以我们可以确定它的这个着色器很慢(你可以通过将它作为一个快速破解的传递着色器来测试它...我建议习惯使用虽然分析师)。 It seem exceedingly odd that such a straightforward shader is slow. 这样一个简单的着色器很慢似乎非常奇怪。

Otherwise, if your texture truly only has those 4 colors (and it is guaranteed), then you can trivially take the number of inRange calls down from 12 to 3 by removing the if from the last branch (just make it an else), and then only testing the r value of c. 否则,如果你的纹理真的只有那4种颜色(并且它是有保证的),那么你可以通过从最后一个分支中删除if来简单地将inRange调用的数量从12减少到3(只需将其设为其他),并且然后只测试c的r值。 I don't know how the iPhone's glsl optimizer works, but then you could further try to replace the if statements with ternary operators and see if that makes a difference. 我不知道iPhone的glsl优化器是如何工作的,但是你可以进一步尝试用三元运算符替换if语句,看看是否有所作为。 Those are the only changes I can think of and unfortunately you can't do the definite optimization if your textures aren't guaranteed to only have those 4 colors. 这些是我能想到的唯一变化,不幸的是,如果你的纹理不能保证只有那4种颜色,你就无法做出明确的优化。

I would again like to point out that you should make sure this shader is causing the slowdown before trying to optimize. 我想再次指出,在尝试优化之前,你应该确保这个着色器导致减速。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM