GLSL着色器-更改色相/饱和度/亮度


14

我正在尝试使用GLSL片段着色器更改图像的色调。我想实现类似于Photoshop的“色相/饱和度调整”层的功能。

在下图中,您可以看到到目前为止我得到了什么。我想更改绿色正方形的色相,使其看起来像右边的红色正方形,但是使用此着色器,我得到一个半红色的半粉红色正方形(中间的正方形)。
在此处输入图片说明

我在片段着色器中所做的就是将纹理的颜色转换为HSV,然后将从顶点着色器获得的HSV颜色添加到其中,然后将颜色转换回RGB。
我究竟做错了什么?

片段着色器:

precision mediump float;
varying vec2 vTextureCoord;
varying vec3 vHSV;
uniform sampler2D sTexture;

vec3 convertRGBtoHSV(vec3 rgbColor) {
    float r = rgbColor[0];
    float g = rgbColor[1];
    float b = rgbColor[2];
    float colorMax = max(max(r,g), b);
    float colorMin = min(min(r,g), b);
    float delta = colorMax - colorMin;
    float h = 0.0;
    float s = 0.0;
    float v = colorMax;
    vec3 hsv = vec3(0.0);
    if (colorMax != 0.0) {
      s = (colorMax - colorMin ) / colorMax;
    }
    if (delta != 0.0) {
        if (r == colorMax) {
            h = (g - b) / delta;
        } else if (g == colorMax) {        
            h = 2.0 + (b - r) / delta;
        } else {    
            h = 4.0 + (r - g) / delta;
        }
        h *= 60.0;
        if (h < 0.0) {
            h += 360.0;
        }
    }
    hsv[0] = h;
    hsv[1] = s;
    hsv[2] = v;
    return hsv;
}
vec3 convertHSVtoRGB(vec3 hsvColor) {
    float h = hsvColor.x;
    float s = hsvColor.y;
    float v = hsvColor.z;
    if (s == 0.0) {
        return vec3(v, v, v);
    }
    if (h == 360.0) {
        h = 0.0;
    }
    int hi = int(h);
    float f = h - float(hi);
    float p = v * (1.0 - s);
    float q = v * (1.0 - (s * f));
    float t = v * (1.0 - (s * (1.0 - f)));
    vec3 rgb;
    if (hi == 0) {
        rgb = vec3(v, t, p);
    } else if (hi == 1) {
        rgb = vec3(q, v, p);
    } else if (hi == 2) {
        rgb = vec3(p, v, t);
    } if(hi == 3) {
        rgb = vec3(p, q, v);
    } else if (hi == 4) {
        rgb = vec3(t, p, v);
    } else {
        rgb = vec3(v, p, q);
    }
    return rgb;
}
void main() {
    vec4 textureColor = texture2D(sTexture, vTextureCoord);
    vec3 fragRGB = textureColor.rgb;
    vec3 fragHSV = convertRGBtoHSV(fragRGB);
    fragHSV += vHSV;
    fragHSV.x = mod(fragHSV.x, 360.0);
    fragHSV.y = mod(fragHSV.y, 1.0);
    fragHSV.z = mod(fragHSV.z, 1.0);
    fragRGB = convertHSVtoRGB(fragHSV);
    gl_FragColor = vec4(convertHSVtoRGB(fragHSV), textureColor.w);
}

编辑: 使用Sam Hocevar在他的答案中提供的功能,解决了粉红色带的问题,但我只能达到色谱的一半。我可以将色相从红色更改为绿色,但不能将其更改为蓝色或粉红色。 在此处输入图片说明

在片段着色器中,我现在正在这样做:

void main() {
    vec4 textureColor = texture2D(sTexture, vTextureCoord);
    vec3 fragRGB = textureColor.rgb;
    vec3 fragHSV = rgb2hsv(fragRGB);
    float h = vHSV.x / 360.0;
    fragHSV.x *= h;
    fragHSV.yz *= vHSV.yz;
    fragHSV.x = mod(fragHSV.x, 1.0);
    fragHSV.y = mod(fragHSV.y, 1.0);
    fragHSV.z = mod(fragHSV.z, 1.0);
    fragRGB = hsv2rgb(fragHSV);
    gl_FragColor = vec4(hsv2rgb(fragHSV), textureColor.w);
}

int hi = int(h/60.0); float f = h/60.0 - float(hi);不是要代替int hi = int(h); float f = h - float(hi);吗?但是,不知道是否是它的原因。
kolrabi

@kolrabi我已经尝试过了,但是我仍然得到粉红色的乐队。我终于用Sam Hocevar在回答中提供的转换函数解决了这个问题。
miviclin

@mivic:我们不会在问题中回答。如果您自己找到答案,则发布问题的答案。
Nicol Bolas

Answers:


22

这些功能将非常糟糕。我建议使用考虑到GPU编写的功能。这是我的:

vec3 rgb2hsv(vec3 c)
{
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

请注意,对于这些功能,范围H是[0…1]而不是[0…360],因此您必须调整输入。

来源:http//lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl


使用这些功能解决了问题。没有更多的粉红色乐队。但我认为我仍然在做错事。我已经编辑了原始帖子,并提供了更多信息。谢谢。
miviclin

1
十分有趣!您能解释为什么这些功能表现更好吗?就像“牢记GPU”一样?
Marco Marco

4
@Marco GPU不太擅长处理大型if()构造,但擅长矢量运算(对多个标量值的并行运算)。上面的函数从不使用if(),尝试并行化操作,并且通常使用较少的指令。这些通常是很好的指标,它们会变得更快。
sam hocevar

这些函数是完全等同于标准HSV公式(忽略舍入误差),还是近似值?
Stefan Monov

3

正如Nicol Bolas在原始帖子的评论中建议的那样,我将问题的解决方案发布在单独的答案中。

如原始帖子中的图像所示,第一个问题是用粉红色带渲染的图像。我使用Sam Hocevar在他的答案中提供的功能对其进行了修复(/gamedev//a/59808/22302)中。

第二个问题是,我将纹理像素的色相乘以我发送给着色器的值,这意味着与纹理像素色相的偏移,因此我必须执行加法运算而不是乘法运算。
我仍然对饱和度和亮度进行乘法运算,因为否则我会得到一个怪异的行为,并且我真的不需要将它们增加到比原始纹理的饱和度或亮度更高的水平。

这是我现在正在使用的着色器的main()方法。这样,我可以将色相从0º更改为360º,使图像去饱和并降低亮度。

void main() {
    vec4 textureColor = texture2D(sTexture, vTextureCoord);
    vec3 fragRGB = textureColor.rgb;
    vec3 fragHSV = rgb2hsv(fragRGB).xyz;
    fragHSV.x += vHSV.x / 360.0;
    fragHSV.yz *= vHSV.yz;
    fragHSV.xyz = mod(fragHSV.xyz, 1.0);
    fragRGB = hsv2rgb(fragHSV);
    gl_FragColor = vec4(fragRGB, textureColor.w);
} 

2
提示:您可能想要mod()色相,但您可能想要饱和度和亮度clamp()
Gustavo Maciel 2014年
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.