2012-02-10 14 views
21

¿Hay alguna forma de cambiar de manera eficiente el tono de una textura 2D OpenGL usando GLSL (fragment shader)?¿Cómo cambiar el tono de una textura con GLSL?

¿Alguien tiene un código para ello?

ACTUALIZACIÓN: Este es el código resultante de user1118321 sugerencia:

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

Y este es el resultado se compara con una referencia:

enter image description here

he tratado de cambiar I con Q dentro de atan pero el resultado es incorrecto incluso alrededor de 0 °

¿Tiene usted alguna pista?

Si es necesario para la comparación, esta es la imagen original sin modificar: enter image description here

+5

Es un giro de la triple RGB alrededor de la (1,1,1) del vector - usted puede expresar que, como la multiplicación de matrices en el shader – Flexo

+0

Si su cambio de tono es constante, entonces se puede omitir el atan, sqrt, sin y cos. Lo que estás haciendo es convertirlos en coordenadas polares, agregarlos al ángulo y convertirlos nuevamente. Puedes hacer esto con una matriz de rotación de 2x2. Si no necesita hacer ninguna otra operación matemática en el espacio yiq, puede precalcular toda la transformación.Multiplique su rgb2yiq con la rotación 2d (acolchada a un 3x3) y luego con el yiq2rgb para obtener una matriz grande de 3x3 que haga todo el proceso. Lo cual, como dice @Flexo, es solo una rotación alrededor del vector (1,1,1). –

Respuesta

23

Mientras lo @awoodland dice es correcto, este método puede causar problemas con los cambios de luminancia, creo.

Los sistemas de color HSV y HLS son problemáticos por varias razones. Hablé con un científico del color sobre esto recientemente, y su recomendación fue convertir al espacio YIQ o YCbCr y ajustar los canales de crominancia (I & Q, o Cb & Cr) en consecuencia. (Usted puede aprender cómo hacer que here y here.)

Una vez en uno de esos espacios, se puede obtener la tonalidad del ángulo formado por los canales de crominancia, haciendo hue = atan(cr/cb) (ver para CB == 0). Esto te da un valor en radianes. Simplemente gírelo agregando la cantidad de rotación del tono. Una vez que haya hecho eso, puede calcular la magnitud del croma con chroma = sqrt(cr*cr+cb*cb). Para volver a RGB, calcule el nuevo Cb y Cr (o I & Q) usando Cr = chroma * sin (hue), Cb = chroma * cos (hue). A continuación, vuelva a convertir a RGB como se describe en las páginas web anteriores.

EDITAR: Esta es una solución que he probado y parece darme los mismos resultados que su referencia. Es probable que pueda colapsar algunos de los productos escalares en multiplica la matriz:

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

Gracias, acabo de implementarlo, pero parece que no funciona correctamente. En realidad da un cambio de tono pero es completamente diferente del ajuste de tono de Photoshop (por ejemplo). Decidí convertirlo en YIQ porque con YCbCr estaba teniendo incluso los peores resultados. Por lo tanto, he calculado 'hue = atan2 (Q, I)', 'chroma = sqrt (I * I + Q * Q)' y luego 'I = chroma * sin (hue)', 'Q = chorma * cos (hue) '. ¿Es correcto? ¿Me estoy perdiendo de algo? – Andrea3000

+0

Eso me parece correcto. ¿Qué resultados estás obteniendo? (¿El matiz está mal, o la luminancia y la saturación también se estropean?) ¿Los resultados se parecen más a lo que espera si invierte I y Q en la llamada 'atan2()'? Tal vez si publicaste algún código, podríamos verificarlo por ti. – user1118321

+0

Gracias por su atención, he actualizado la pregunta con un código y una instantánea del resultado. – Andrea3000

3

Andrea3000, en la comparación de ejemplos YIQ en la red, me encontré con tu publicación, pero creo que hay un problema con la versión 'actualizada' de su código ... estoy seguro de que sus definiciones 'mat3' son flip/flop en el ordenamiento de columna/fila ... (tal vez por eso todavía tenía problemas) ...

FYI: orden de matriz OpenGL : "Para más valores, las matrices se completan en orden de columna mayor. Es decir, los primeros valores X son la primera columna, los segundos valores X son la columna siguiente, y así sucesivamente". Ver: http://www.opengl.org/wiki/GLSL_Types

mat2(
float, float, //first column 
float, float); //second column 
Cuestiones relacionadas