Sombreador GLSL - Cambiar tono / saturación / brillo

14

Estoy tratando de cambiar el tono de una imagen usando un sombreador de fragmentos GLSL. Quiero lograr algo similar a la capa de ajuste de tono / saturación de Photoshop.

En la siguiente imagen puedes ver lo que tengo hasta ahora. Quiero cambiar el tono del cuadrado verde para que se vea como el cuadrado rojo a la derecha, pero con este sombreador obtengo un cuadrado medio rojo medio rosado (el cuadrado en el medio).
ingrese la descripción de la imagen aquí

Lo que estoy haciendo en el sombreador de fragmentos es convertir el color de la textura a HSV, luego agrego el color HSV que obtengo del sombreador de vértices y vuelvo a convertir el color a RGB.
¿Qué estoy haciendo mal?

Sombreador de fragmentos:

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

EDITAR: Usando las funciones que Sam Hocevar proporcionó en su respuesta, el problema con las bandas de color rosa está resuelto, pero solo puedo alcanzar la mitad del espectro de color. Puedo cambiar el tono de rojo a verde, pero no puedo cambiarlo a azul o rosa. ingrese la descripción de la imagen aquí

En el fragment shader, estoy haciendo esto ahora:

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);
}
miviclin
fuente
¿No quisiste decir en int hi = int(h/60.0); float f = h/60.0 - float(hi);lugar de int hi = int(h); float f = h - float(hi);? Sin embargo, no sé si eso lo está causando.
kolrabi
@kolrabi Lo he intentado pero todavía recibía bandas rosas. Finalmente resolví ese problema con las funciones de conversión que Sam Hocevar proporcionó en su respuesta.
miviclin
@mivic: No ponemos respuestas en las preguntas. Si encontró la respuesta por su cuenta, publique una respuesta a su pregunta.
Nicol Bolas

Respuestas:

22

Estas funciones funcionarán muy mal. Sugiero usar funciones que están escritas con la GPU en mente. Aquí están los míos:

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

Tenga en cuenta que para estas funciones el rango Hes [0 ... 1] en lugar de [0 ... 360], por lo que tendrá que adaptar su entrada.

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

sam hocevar
fuente
El uso de estas funciones resolvió el problema. No más bandas rosadas. Pero creo que todavía estoy haciendo algo mal. He editado mi publicación original con más información. Gracias.
miviclin
1
¡Muy interesante! ¿Puedes explicar por qué estas funciones funcionan mejor? ¿Cómo es "tener la GPU en mente"?
Marco
44
Las GPU @Marco no son muy buenas para manejar if()construcciones grandes , pero son buenas para operaciones vectoriales (operaciones paralelas en varios valores escalares). Las funciones anteriores nunca se usan if(), intentan paralelizar operaciones y, en general, usan menos instrucciones. Estos suelen ser buenos indicadores de que serán más rápidos.
sam hocevar
¿Son estas funciones exactamente equivalentes a las fórmulas estándar de HSV (ignorando los errores de redondeo), o son una aproximación?
Stefan Monov
3

Como Nicol Bolas sugirió en los comentarios de la publicación original, estoy publicando la solución a mi problema en una respuesta separada.

El primer problema fue que la imagen se representaba con bandas rosas, como lo muestra la imagen en la publicación original. Lo arreglé usando las funciones que Sam Hocevar proporcionó en su respuesta ( /gamedev//a/59808/22302 ).

El segundo problema fue que estaba multiplicando el tono del píxel de la textura por el valor que estaba enviando al sombreador, que está destinado a ser un desplazamiento del tono del píxel de las texturas, por lo que tuve que realizar una adición en lugar de una multiplicación.
Todavía realizo una multiplicación por saturación y brillo porque de lo contrario obtengo un comportamiento extraño, y realmente no necesito incrementarlos más allá de la saturación o brillo de la textura original en este momento.

Este es el método main () del sombreador que estoy usando en este momento. Con esto puedo cambiar el tono de 0º a 360º, desaturar la imagen y reducir el brillo.

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);
} 
miviclin
fuente
2
consejo: probablemente quieras mod()el tono, pero la saturación y el brillo quizás quieras clamp()hacerlo.
Gustavo Maciel