GLSL-Shader - Ändern Sie Farbton / Sättigung / Helligkeit

14

Ich versuche, den Farbton eines Bildes mit einem GLSL-Fragment-Shader zu ändern. Ich möchte etwas erreichen, das der Ebene „Farbton- / Sättigungsanpassung“ in Photoshop ähnelt.

Im folgenden Bild sehen Sie, was ich bisher habe. Ich möchte den Farbton des grünen Quadrats so ändern, dass er wie das rote Quadrat auf der rechten Seite aussieht, aber mit diesem Shader erhalte ich ein halb rotes, halb rosa Quadrat (das Quadrat in der Mitte).
Bildbeschreibung hier eingeben

Was ich im Fragment-Shader mache, ist das Konvertieren der Texturfarbe in HSV. Dann füge ich die HSV-Farbe hinzu, die ich vom Vertex-Shader erhalte, und konvertiere die Farbe zurück in RGB.
Was mache ich falsch?

Fragment-Shader:

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: Mit den Funktionen, die Sam Hocevar in seiner Antwort zur Verfügung stellte, ist das Problem mit den rosa Bändern gelöst, aber ich kann nur die Hälfte des Farbspektrums erreichen. Ich kann den Farbton von Rot auf Grün ändern, aber nicht auf Blau oder Rosa. Bildbeschreibung hier eingeben

Im Fragment-Shader mache ich das jetzt:

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
quelle
Meinten Sie nicht int hi = int(h/60.0); float f = h/60.0 - float(hi);statt int hi = int(h); float f = h - float(hi);? Ich weiß allerdings nicht, ob das die Ursache ist.
Kolrabi
@kolrabi Ich habe das versucht, aber ich bekam immer noch rosa Bänder. Ich habe dieses Problem endlich mit den Konvertierungsfunktionen gelöst, die Sam Hocevar in seiner Antwort bereitgestellt hat.
Miviclin
@mivic: Wir geben keine Antworten auf Fragen. Wenn Sie die Antwort selbst gefunden haben, senden Sie eine Antwort auf Ihre Frage.
Nicol Bolas

Antworten:

22

Diese Funktionen werden sehr schlecht ausgeführt. Ich schlage vor, Funktionen zu verwenden, die für die GPU geschrieben wurden. Hier sind meine:

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

Beachten Sie, dass für diese Funktionen der Bereich für H [0… 1] anstelle von [0… 360] ist, sodass Sie Ihre Eingabe anpassen müssen.

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

sam hocevar
quelle
Die Verwendung dieser Funktionen löste das Problem. Keine rosa Bänder mehr. Aber ich glaube, ich mache immer noch etwas falsch. Ich habe meinen ursprünglichen Beitrag mit weiteren Informationen bearbeitet. Vielen Dank.
Miviclin
1
Wirklich interessant! Können Sie erklären, warum diese Funktionen eine bessere Leistung erbringen? Was bedeutet "die GPU im Auge haben"?
Marco
4
@Marco-GPUs können mit großen if()Konstrukten nicht sehr gut umgehen , aber mit Vektoroperationen (parallele Operationen mit mehreren Skalarwerten). Die oben genannten Funktionen verwenden niemals if()Operationen, versuchen diese zu parallelisieren und verwenden im Allgemeinen weniger Anweisungen. Dies sind normalerweise gute Indikatoren dafür, dass sie schneller sein werden.
Sam Hocevar
Entsprechen diese Funktionen exakt den HSV-Standardformeln (ohne Berücksichtigung von Rundungsfehlern) oder handelt es sich um eine Annäherung?
Stefan Monov
3

Wie Nicol Bolas in den Kommentaren des ursprünglichen Posts angedeutet hat, veröffentliche ich die Lösung meines Problems in einer separaten Antwort.

Die erste Ausgabe war das Bild, das mit rosa Bändern gerendert wurde, wie das Bild im Originalbeitrag zeigt. Ich habe das Problem mithilfe der Funktionen behoben, die Sam Hocevar in seiner Antwort bereitgestellt hat ( /gamedev//a/59808/22302 ).

Das zweite Problem war, dass ich den Farbton des Texturpixels mit dem Wert multiplizierte, den ich an den Shader sendete. Dieser Wert soll ein Versatz zum Pixelfarbton der Textur sein, sodass ich anstelle einer Multiplikation eine Addition durchführen musste.
Ich führe immer noch eine Multiplikation für Sättigung und Helligkeit durch, weil ich sonst ein seltsames Verhalten bekomme und ich brauche sie im Moment nicht wirklich weiter zu erhöhen als die Sättigung oder Helligkeit der ursprünglichen Textur.

Dies ist die main () -Methode des Shaders, den ich gerade verwende. Auf diese Weise kann ich den Farbton von 0º auf 360º verschieben, das Bild entsättigen und die Helligkeit verringern.

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
quelle
2
tipp: du willst wahrscheinlich mod()den farbton, aber sättigung und helligkeit willst du vielleicht clamp()stattdessen.
Gustavo Maciel