GLSL Shader - Zmień Odcień / Nasycenie / Jasność

14

Próbuję zmienić odcień obrazu za pomocą modułu cieniującego fragmenty GLSL. Chcę osiągnąć coś podobnego do warstwy korekty odcienia / nasycenia w Photoshopie.

Na poniższym obrazku możesz zobaczyć, co mam do tej pory. Chcę zmienić odcień zielonego kwadratu, aby wyglądał jak czerwony kwadrat po prawej stronie, ale za pomocą tego modułu cieniującego dostaję pół czerwony pół różowy kwadrat (kwadrat na środku).
wprowadź opis zdjęcia tutaj

To, co robię w module cieniującym fragmenty, polega na konwersji koloru tekstury na HSV, a następnie dodaję do niego kolor HSV, który otrzymuję z modułu cieniującego wierzchołki, i przekształcam kolor z powrotem na RGB.
Co ja robię źle?

Moduł cieniujący fragmenty:

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

EDYCJA: Korzystając z funkcji Sama Hocevara podanych w jego odpowiedzi, problem z różowymi pasmami został rozwiązany, ale mogę osiągnąć tylko połowę spektrum kolorów. Mogę zmienić odcień z czerwonego na zielony, ale nie mogę go zmienić na niebieski lub różowy. wprowadź opis zdjęcia tutaj

W shaderze fragmentów robię to teraz:

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
źródło
Nie miałeś na myśli int hi = int(h/60.0); float f = h/60.0 - float(hi);zamiast int hi = int(h); float f = h - float(hi);? Nie wiem jednak, czy to powoduje.
kolrabi
@kolrabi Próbowałem tego, ale wciąż otrzymywałem różowe zespoły. W końcu rozwiązałem ten problem z funkcjami konwersji, które Sam Hocevar podał w swojej odpowiedzi.
miviclin
@mivic: Nie stawiamy odpowiedzi na pytania. Jeśli znalazłeś odpowiedź na własną rękę, opublikuj odpowiedź na swoje pytanie.
Nicol Bolas,

Odpowiedzi:

22

Funkcje te będą działać bardzo źle. Sugeruję używanie funkcji napisanych z myślą o GPU. Oto moje:

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

Pamiętaj, że dla tych funkcji zakres Hwynosi [0… 1] zamiast [0… 360], więc będziesz musiał dostosować swoje dane wejściowe.

Źródło: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl

sam hocevar
źródło
Korzystanie z tych funkcji rozwiązało problem. Nigdy więcej różowych pasm. Ale myślę, że nadal robię coś złego. Zredagowałem swój oryginalny post z dodatkowymi informacjami. Dzięki.
miviclin
1
Naprawdę interesujące! Czy możesz wyjaśnić, dlaczego te funkcje działają lepiej? Jak to jest „mieć na uwadze GPU”?
Marco
4
GPU @Marco nie są zbyt dobre w obsłudze dużych if()konstrukcji, ale są dobre w operacjach wektorowych (operacje równoległe na kilku wartościach skalarnych). Powyższe funkcje nigdy nie używają if(), próbują zrównoważyć operacje i ogólnie używają mniej instrukcji. Są to zwykle dobre wskaźniki, że będą szybsze.
sam hocevar
Czy te funkcje są dokładnie równoważne standardowym formułom HSV (ignorowanie błędów zaokrąglania), czy też są przybliżeniem?
Stefan Monov
3

Jak zasugerował Nicol Bolas w komentarzach do oryginalnego postu, rozwiązanie mojego problemu zamieszczam w osobnej odpowiedzi.

Pierwszy problem polegał na renderowaniu obrazu za pomocą różowych pasków, jak pokazano w oryginalnym poście. Naprawiłem to za pomocą funkcji Sama Hocevara podanych w jego odpowiedzi ( /gamedev//a/59808/22302 ).

Drugi problem polegał na tym, że mnożałem odcień piksela tekstury przez wartość, którą wysyłałem do modułu cieniującego, która ma być przesunięciem względem odcienia piksela tekstury, więc musiałem wykonać dodawanie zamiast mnożenia.
Nadal wykonuję mnożenie dla nasycenia i jasności, ponieważ w przeciwnym razie mam dziwne zachowanie i tak naprawdę nie potrzebuję zwiększać ich dalej niż nasycenie lub jasność oryginalnej tekstury.

To jest główna () metoda modułu cieniującego, którego teraz używam. Dzięki temu mogę zmieniać odcień od 0º do 360º, desaturować obraz i zmniejszać jasność.

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
źródło
2
wskazówka: prawdopodobnie chcesz mod()odcień, ale clamp()zamiast tego możesz chcieć nasycenia i jasności .
Gustavo Maciel