GLSL Shader - Changer la teinte / la saturation / la luminosité

14

J'essaie de changer la teinte d'une image à l'aide d'un shader de fragment GLSL. Je veux réaliser quelque chose de similaire au calque de réglage de la teinte / saturation de Photoshop.

Dans l'image suivante, vous pouvez voir ce que j'ai jusqu'à présent. Je veux changer la teinte du carré vert pour qu'il ressemble au carré rouge à droite, mais avec ce shader, j'obtiens un carré moitié rouge moitié rose (le carré au milieu).
entrez la description de l'image ici

Ce que je fais dans le fragment shader, c'est la conversion de la couleur de la texture en HSV, puis j'y ajoute la couleur HSV que j'obtiens du vertex shader et je reconvertis la couleur en RVB.
Qu'est-ce que je fais mal?

Shader de fragment:

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

ÉDITER: En utilisant les fonctions fournies par Sam Hocevar dans sa réponse, le problème avec les bandes roses est résolu, mais je ne peux atteindre que la moitié du spectre de couleurs. Je peux changer la teinte du rouge au vert, mais je ne peux pas la changer en bleu ou rose. entrez la description de l'image ici

Dans le fragment shader, je fais ceci maintenant:

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
la source
Vous ne vouliez pas dire à la int hi = int(h/60.0); float f = h/60.0 - float(hi);place de int hi = int(h); float f = h - float(hi);? Je ne sais pas si cela en est la cause.
kolrabi
@kolrabi J'ai essayé ça mais je recevais toujours des bandes roses. J'ai finalement résolu ce problème avec les fonctions de conversion que Sam Hocevar a fournies dans sa réponse.
miviclin
@mivic: Nous ne mettons pas de réponses dans les questions. Si vous avez trouvé la réponse par vous-même, postez une réponse à votre question.
Nicol Bolas

Réponses:

22

Ces fonctions fonctionneront très mal. Je suggère d'utiliser des fonctions écrites avec le GPU à l'esprit. Voici les miens:

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

Notez que pour ces fonctions, la plage de H[0… 1] au lieu de [0… 360], vous devrez donc adapter votre entrée.

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

sam hocevar
la source
L'utilisation de ces fonctions a résolu le problème. Plus de bandes roses. Mais je pense que je fais toujours quelque chose de mal. J'ai édité mon message d'origine avec plus d'informations. Merci.
miviclin
1
Vraiment intéressant! Pouvez-vous expliquer pourquoi ces fonctions fonctionnent mieux? À quoi ressemble «avoir le GPU à l'esprit»?
Marco
4
Les GPU @Marco ne sont pas très bons pour gérer les grandes if()constructions, mais sont bons pour les opérations vectorielles (opérations parallèles sur plusieurs valeurs scalaires). Les fonctions ci-dessus n'utilisent jamais if(), essaient de paralléliser les opérations et, en général, elles utilisent moins d'instructions. Ce sont généralement de bons indicateurs qu'ils vont être plus rapides.
sam hocevar
Ces fonctions sont-elles exactement équivalentes aux formules HSV standard (en ignorant les erreurs d'arrondi), ou s'agit-il d'une approximation?
Stefan Monov
3

Comme Nicol Bolas l'a suggéré dans les commentaires du post original, je poste la solution à mon problème dans une réponse séparée.

Le premier problème était que l'image était rendue avec des bandes roses, comme le montre l'image dans le message d'origine. Je l'ai corrigé en utilisant les fonctions fournies par Sam Hocevar dans sa réponse ( /gamedev//a/59808/22302 ).

Le deuxième problème était que je multipliais la teinte du pixel de la texture par la valeur que j'envoyais au shader, ce qui est censé être un décalage par rapport à la teinte du pixel des textures, j'ai donc dû effectuer un ajout au lieu d'une multiplication.
J'effectue toujours une multiplication pour la saturation et la luminosité, car j'obtiens un comportement étrange autrement, et je n'ai pas vraiment besoin de les augmenter plus que la saturation ou la luminosité de la texture d'origine pour le moment.

Il s'agit de la méthode main () du shader que j'utilise actuellement. Avec cela, je peux changer la teinte de 0º à 360º, désaturer l'image et réduire la luminosité.

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
la source
2
astuce: vous voudrez probablement mod()la teinte, mais la saturation et la luminosité vous voudrez peut-être à la clamp()place.
Gustavo Maciel