2012-02-10 22 views
21

C'è un modo per cambiare in modo efficiente la tonalità di una trama OpenGL 2D usando GLSL (framment shader)?Come cambiare la tonalità di una trama con GLSL?

Qualcuno ha un codice per questo?

UPDATE: Questo è il codice risultante da user1118321 suggerimento:

uniform sampler2DRect texture; 
const mat3 rgb2yiq = mat3(0.299, 0.587, 0.114, 0.595716, -0.274453, -0.321263, 0.211456, -0.522591, 0.311135); 
const mat3 yiq2rgb = mat3(1.0, 0.9563, 0.6210, 1.0, -0.2721, -0.6474, 1.0, -1.1070, 1.7046); 
uniform float hue; 

void main() { 

vec3 yColor = rgb2yiq * texture2DRect(texture, gl_TexCoord[0].st).rgb; 

float originalHue = atan(yColor.b, yColor.g); 
float finalHue = originalHue + hue; 

float chroma = sqrt(yColor.b*yColor.b+yColor.g*yColor.g); 

vec3 yFinalColor = vec3(yColor.r, chroma * cos(finalHue), chroma * sin(finalHue)); 
gl_FragColor = vec4(yiq2rgb*yFinalColor, 1.0); 
} 

E questo è il risultato confrontato con un riferimento:

enter image description here

ho cercato di commutare I con Q dentro atan ma il risultato è sbagliato anche intorno a 0 °

Avete qualche suggerimento?

se necessario per il confronto, questa è l'immagine originale non modificato: enter image description here

+5

Si tratta di una rotazione del RGB tripla attorno alla (1,1,1) vettore - è possibile esprimere che come moltiplicazione di matrici nello shader – Flexo

+0

Se il cambiamento di tonalità è costante allora si può saltare la atan, sqrt, sin, e cos. Quello che stai facendo è convertirli in coordinate polari, aggiungere all'angolo e riconvertire. Puoi fare questo calcolo con una matrice di rotazione 2x2. Se non è necessario eseguire altri calcoli matematici nello spazio yiq, è possibile precalcolare l'intera trasformazione.Moltiplica il tuo rgb2yiq con la rotazione 2d (estesa a 3x3) e poi con yiq2rgb per ottenere una grande matrice 3x3 che fa l'intero processo. Che, come dice @Flexo, è solo una rotazione attorno al vettore (1,1,1). –

risposta

23

Mentre ciò @awoodland dice è corretto, che il metodo può causare problemi con i cambiamenti nella luminanza, credo.

I sistemi di colori HSV e HLS sono problematici per una serie di motivi. Ne ho parlato recentemente con uno scienziato del colore, e il suo consiglio era di convertire nello spazio YIQ o YCbCr e regolare i canali di crominanza (I & Q, o Cb & Cr) di conseguenza. (Si può imparare come fare here e here.)

Una volta in uno di questi spazi, è possibile ottenere la tonalità dal angolo formato dai canali chroma, facendo hue = atan(cr/cb) (guardando per cb == 0). Questo ti dà un valore in radianti. Basta ruotarlo aggiungendo la quantità di rotazione della tonalità. Dopo averlo fatto, puoi calcolare la grandezza del croma con chroma = sqrt(cr*cr+cb*cb). Per tornare a RGB, calcolare il nuovo Cb e Cr (o I & Q) utilizzando Cr = chroma * sin (hue), Cb = chroma * cos (hue). Quindi riconvertire in RGB come descritto nelle pagine Web sopra.

EDIT: Ecco una soluzione che ho testato e sembra darmi gli stessi risultati del tuo riferimento. Probabilmente si può comprimere alcuni dei prodotti dot in moltiplica matrice:

uniform sampler2DRect inputTexture; 
uniform float hueAdjust; 
void main() 
{ 
    const vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0); 
    const vec4 kRGBToI  = vec4 (0.596, -0.275, -0.321, 0.0); 
    const vec4 kRGBToQ  = vec4 (0.212, -0.523, 0.311, 0.0); 

    const vec4 kYIQToR = vec4 (1.0, 0.956, 0.621, 0.0); 
    const vec4 kYIQToG = vec4 (1.0, -0.272, -0.647, 0.0); 
    const vec4 kYIQToB = vec4 (1.0, -1.107, 1.704, 0.0); 

    // Sample the input pixel 
    vec4 color = texture2DRect (inputTexture, gl_TexCoord [ 0 ].xy); 

    // Convert to YIQ 
    float YPrime = dot (color, kRGBToYPrime); 
    float I  = dot (color, kRGBToI); 
    float Q  = dot (color, kRGBToQ); 

    // Calculate the hue and chroma 
    float hue  = atan (Q, I); 
    float chroma = sqrt (I * I + Q * Q); 

    // Make the user's adjustments 
    hue += hueAdjust; 

    // Convert back to YIQ 
    Q = chroma * sin (hue); 
    I = chroma * cos (hue); 

    // Convert back to RGB 
    vec4 yIQ = vec4 (YPrime, I, Q, 0.0); 
    color.r = dot (yIQ, kYIQToR); 
    color.g = dot (yIQ, kYIQToG); 
    color.b = dot (yIQ, kYIQToB); 

    // Save the result 
    gl_FragColor = color; 
} 
+0

Grazie, l'ho appena implementato ma non sembra funzionare correttamente. In realtà dà un cambiamento di tonalità, ma è completamente diverso dalla regolazione della tonalità di Photoshop (per esempio). Ho deciso di convertirlo in YIQ perché con YCbCr avevo anche risultati peggiori. Quindi ho calcolato 'hue = atan2 (Q, I)', 'chroma = sqrt (I * I + Q * Q)' e poi 'I = chroma * sin (hue)', 'Q = chorma * cos (tonalità) '. È corretto? Mi sto perdendo qualcosa? – Andrea3000

+0

Mi sembra corretto. Che risultati ottieni? (La tinta è sbagliata, o anche la luminanza e la saturazione sono confuse?) I risultati sembrano più simili a quelli che ti aspetti se inverti I e Q nella chiamata 'atan2()'? Forse se hai postato del codice, potremmo ricontrollarlo per te. – user1118321

+0

Grazie per l'attenzione, ho aggiornato la domanda con il codice e un'istantanea del risultato. – Andrea3000

3

Andrea3000, nel confronto tra esempi YiQ in rete, mi sono imbattuto in un commento, ma penso che ci sia un problema con la versione 'aggiornata' di il tuo codice .... sono sicuro che le tue definizioni 'mat3' sono flip/flopped sull'ordinamento di colonne/righe ... (forse è per questo che avevi ancora problemi) ...

FYI: ordinamento matrice OpenGL : "Per ulteriori valori, le matrici vengono riempite nell'ordine delle colonne principali, ovvero i primi valori X sono la prima colonna, i secondi valori X sono la colonna successiva e così via." Vedere: http://www.opengl.org/wiki/GLSL_Types

mat2(
float, float, //first column 
float, float); //second column 
Problemi correlati