GLSL Shader - Alterar matiz / saturação / brilho

14

Estou tentando alterar o tom de uma imagem usando um shader de fragmento GLSL. Quero obter algo semelhante à camada de ajuste de matiz / saturação do Photoshop.

Na imagem a seguir, você pode ver o que tenho até agora. Quero alterar a tonalidade do quadrado verde para que ele se pareça com o quadrado vermelho à direita, mas com esse sombreador recebo um quadrado meio vermelho meio rosa (o quadrado no meio).
insira a descrição da imagem aqui

O que estou fazendo no shader de fragmento é converter a cor da textura em HSV, depois adiciono a cor HSV que recebo do shader de vértice e converto a cor novamente em RGB.
O que estou fazendo de errado?

Shader de fragmento:

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

EDIT: Usando as funções que Sam Hocevar forneceu em sua resposta, o problema com as faixas cor-de-rosa está resolvido, mas só consigo atingir metade do espectro de cores. Posso alterar o tom de vermelho para verde, mas não posso alterá-lo para azul ou rosa. insira a descrição da imagem aqui

No shader de fragmento, estou fazendo isso agora:

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
fonte
Você não quis dizer em int hi = int(h/60.0); float f = h/60.0 - float(hi);vez de int hi = int(h); float f = h - float(hi);? Mas não sei se isso está causando isso.
27413 kolrabi
@kolrabi Eu tentei isso, mas ainda estava recebendo bandas cor de rosa. Finalmente resolvi esse problema com as funções de conversão que Sam Hocevar forneceu em sua resposta.
Miviclin
@mivic: Não colocamos respostas em perguntas. Se você encontrou a resposta por conta própria, poste uma resposta para sua pergunta.
Nicol Bolas

Respostas:

22

Essas funções terão um desempenho muito ruim. Sugiro o uso de funções escritas com a GPU em mente. Aqui estão os meus:

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

Observe que para essas funções o intervalo para Hé [0… 1] em vez de [0… 360], portanto você terá que adaptar sua entrada.

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

sam hocevar
fonte
O uso dessas funções resolveu o problema. Não há mais faixas cor de rosa. Mas acho que ainda estou fazendo algo errado. Eu editei minha postagem original com mais informações. Obrigado.
Miviclin
1
Muito interessante! Você pode explicar por que essas funções têm melhor desempenho? Como é "ter a GPU em mente"?
28613 Marco Marco
4
As GPUs do @Marco não são muito boas para lidar com grandes if()construções, mas são boas para operações vetoriais (operações paralelas em vários valores escalares). As funções acima nunca usam if(), tentam paralelizar operações e, em geral, usam menos instruções. Esses geralmente são bons indicadores de que eles serão mais rápidos.
Sam Hocevar
Essas funções são exatamente equivalentes às fórmulas padrão do HSV (ignorando erros de arredondamento) ou são uma aproximação?
Stefan Monov 27/05
3

Como sugeriu Nicol Bolas nos comentários da postagem original, estou postando a solução para o meu problema em uma resposta separada.

A primeira edição foi a imagem sendo renderizada com faixas cor de rosa, como mostra a imagem no post original. Corrigi-o usando as funções que Sam Hocevar forneceu em sua resposta ( /gamedev//a/59808/22302 ).

A segunda questão era que eu estava multiplicando a tonalidade do pixel da textura pelo valor que estava enviando ao shader, que deveria ser um deslocamento da tonalidade do pixel das texturas, então tive que realizar uma adição em vez de uma multiplicação.
Ainda faço uma multiplicação para saturação e brilho porque, de outra forma, recebo um comportamento estranho, e não preciso incrementá-los mais do que a saturação ou o brilho da textura original no momento.

Este é o método main () do shader que estou usando agora. Com isso, posso mudar o tom de 0º para 360º, dessaturar a imagem e reduzir o brilho.

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
fonte
2
dica: você provavelmente deseja mod()a tonalidade, mas saturação e brilho talvez queira clamp().
Gustavo Maciel