GLSL Shader - Cambia tonalità / saturazione / luminosità


14

Sto cercando di cambiare la tonalità di un'immagine usando uno shader di frammenti GLSL. Voglio ottenere qualcosa di simile al livello di regolazione Tonalità / Saturazione di Photoshop.

Nell'immagine seguente puoi vedere quello che ho finora. Voglio cambiare la tonalità del quadrato verde in modo che assomigli al quadrato rosso sulla destra, ma con questo shader ottengo un quadrato mezzo rosa per metà rosso (il quadrato nel mezzo).
inserisci qui la descrizione dell'immagine

Quello che sto facendo nello shader di frammento è convertire il colore della trama in HSV, quindi aggiungo il colore HSV che ottengo dallo shader di vertice e riconvertisco il colore in RGB.
Che cosa sto facendo di sbagliato?

Shader di frammenti:

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 le funzioni fornite da Sam Hocevar nella sua risposta, il problema con le bande rosa è risolto, ma riesco a raggiungere solo metà dello spettro dei colori. Posso cambiare la tonalità dal rosso al verde, ma non posso cambiarla in blu o rosa. inserisci qui la descrizione dell'immagine

Nello shader di frammenti, lo sto facendo ora:

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

Non intendevi int hi = int(h/60.0); float f = h/60.0 - float(hi);invece int hi = int(h); float f = h - float(hi);? Non so se questo lo stia causando.
Kolrabi,

@kolrabi Ci ho provato ma stavo ancora ottenendo bande rosa. Ho finalmente risolto quel problema con le funzioni di conversione fornite da Sam Hocevar nella sua risposta.
miviclin,

@mivic: non mettiamo le risposte alle domande. Se hai trovato la risposta da solo, pubblica una risposta alla tua domanda.
Nicol Bolas,

Risposte:


22

Queste funzioni funzioneranno molto male. Suggerisco di usare funzioni scritte pensando alla GPU. Ecco i miei:

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

Nota che per queste funzioni l'intervallo Hè [0 ... 1] invece di [0 ... 360], quindi dovrai adattare il tuo input.

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


L'uso di queste funzioni ha risolto il problema. Niente più bande rosa. Ma penso che sto ancora facendo qualcosa di sbagliato. Ho modificato il mio post originale con ulteriori informazioni. Grazie.
miviclin,

1
Molto interessante! Puoi spiegare perché queste funzioni offrono prestazioni migliori? Cosa significa "avere in mente la GPU"?
Marco

4
Le GPU @Marco non sono molto brave a gestire if()costrutti di grandi dimensioni , ma sono brave nelle operazioni vettoriali (operazioni parallele su più valori scalari). Le funzioni sopra non usano mai if(), cercano di parallelizzare le operazioni e in generale usano meno istruzioni. Questi di solito sono buoni indicatori del fatto che saranno più veloci.
Sam Hocevar,

Queste funzioni sono esattamente equivalenti alle formule HSV standard (ignorando gli errori di arrotondamento) o sono un'approssimazione?
Stefan Monov,

3

Come suggerito da Nicol Bolas nei commenti del post originale, sto postando la soluzione al mio problema in una risposta separata.

Il primo problema è stato il rendering dell'immagine con bande rosa, come mostra l'immagine nel post originale. L'ho corretto usando le funzioni fornite da Sam Hocevar nella sua risposta ( /gamedev//a/59808/22302 ).

Il secondo problema era che stavo moltiplicando la tonalità del pixel della trama per il valore che stavo inviando allo shader, che dovrebbe essere un offset rispetto alla tonalità del pixel delle trame, quindi ho dovuto eseguire un'aggiunta anziché una moltiplicazione.
Eseguo ancora una moltiplicazione per saturazione e luminosità perché altrimenti ottengo un comportamento strano e al momento non ho davvero bisogno di incrementarle oltre la saturazione o la luminosità della trama originale.

Questo è il metodo principale () dello shader che sto usando in questo momento. Con questo posso spostare la tonalità da 0º a 360º, desaturare l'immagine e ridurre 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);
} 

2
consiglio: probabilmente vuoi mod()la tonalità, ma invece saturazione e luminosità potresti volerlo clamp().
Gustavo Maciel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.