top of page
Foto del escritorBraulio Madrid

#HSV Hue Saturation Value



Todo este tiempo hemos trabajado con el #EspacioDeColor #RGB que resulta ideal si queremos teñir un color con otro, pero si queremos buscar un tono especifico resulta molesto, pero que hay de otros espacios de color como el de tono saturación y brillo, el #HSV nos permite controlar el tono con mayor facilidad y hay otros espacios de color como el #HSL y el #CIE, algunos similares a HSV y otros mas complejos en cálculos pero que permite una mayor gama de color, en esta oportunidad nos detendremos a analizar que es un espacio de color y que diferencia este en especial respecto al tradicional RGB.


¿Como funciona?


Cuando usamos el modelo #HSV también tenemos 3 componentes que definen nuestro color, pero en este caso se asignan al tono, la saturación y el valor del color. Debido a que el valor máximo y mínimo del mapa de matices al mismo valor (rojo), podemos verlo como un círculo.


Este concepto se puede dar un paso más allá para imaginar el espacio de color como un cilindro donde el tono es la rotación alrededor del centro, la saturación es la proximidad al centro y el valor está representado por la altura relativa del punto en el cilindro.


En el código, podemos representar esto de manera más eficiente tomando el valor absoluto de un valor que primero se multiplica por 6 (porque tiene que alcanzar un valor de 1 sobre el cambio de un sexto) y se desplaza hacia un lado. Los valores verde y azul suben y luego bajan nuevamente en el rango, es por eso que se restan de 2, volteándolos. En cambio, el valor rojo primero disminuye y luego aumenta nuevamente. Para archivar esto, 1 se resta de él.


Después de configurar el aumento y la disminución de los valores, los valores se combinan y se llama a la función de saturación. La función de saturación garantiza que ningún valor sea inferior a 0 o superior a 1.


Si queremos asegurarnos de que los valores de tono por encima de 1 o por debajo de 0 no den como resultado un tono rojo y, en su lugar, envuelva el espectro de color como se esperaba, podemos tomar la parte fráccional del tono e ignorar la parte decimal. En hlsl, la función frac hace exactamente eso.


float3 hue2rgb(float hue) {
    hue = frac(hue); //solo se usa la parte fraccional para hacer un bucle
    float r = abs(hue * 6 - 3) - 1; //red
    float g = 2 - abs(hue * 6 - 2); //green
    float b = 2 - abs(hue * 6 - 4); //blue
    float3 rgb = float3(r,g,b); //combinar componentes
    rgb = saturate(rgb); //asegurar que esté entre 0 o 1
    return rgb;
}

Conversión completa de HSV a RGB


Después de poder convertir el tono en un color #RGB que se vea correcto, a continuación también tenemos que hacer que el color de salida respete la saturación y el valor.


Para aplicar la saturación al color ya generado, hacemos una interpolación lineal de 1 al color y usamos el componente de saturación del vector como argumento.


Dado que 1 representa el blanco completo en este contexto, esto hace que el tono se desvanezca para el color de baja saturación, mientras que se conserva para los de alta saturación.


El último paso a seguir es aplicar el valor. Dado que el valor representa el brillo del color, la operación para aplicarlo es simplemente multiplicar el color hasta ahora por el componente de valor.

float3 hsv2rgb(float3 hsv)
{
    float3 rgb = hue2rgb(hsv.x); //apply hue
    rgb = lerp(1, rgb, hsv.y); //apply saturation
    rgb = rgb * hsv.z; //apply value
    return rgb;
}

Conversión de RGB a HSV


A diferencia de la conversión de RGB a HSV, los datos que estamos usando para generar el color hsv están un poco más entrelazados entre los diferentes componentes del vector de salida, por lo que no lo dividiremos en varias funciones.


Las variables que estamos usando para obtener el tono depende de qué componente del color rgb tenga el valor más alto, además, también necesitamos la diferencia entre el componente más alto y el más bajo para calcularlo.


Entonces, después de calcular los componentes más altos y más bajos del color de entrada a través de las funciones funciones min y max incorporadas y usarlos para obtener la diferencia entre ellos, primero creamos el tono y luego verificamos cuál de los componentes es igual al valor más alto.


Luego restamos los dos valores que no son el valor más alto el uno del otro, los dividimos por la diferencia entre el valor mínimo y máximo y luego sumamos 0, 2 o 4 dependiendo del color que sea el más alto. Luego dividimos el tono resultante por 6 y solo usamos la parte fraccional.


Al obtener el componente más grande, nos aseguramos de que los otros 2 componentes sean el componente mínimo y el componente que está cambiando en el tercero en el que estamos ahora.


Por ejemplo, cuando el rojo es el color más intenso, el azul tiene el valor más bajo y se calcula la diferencia entre verde y azul o el verde tiene el valor más bajo, en ese caso la diferencia resultante tiene un valor negativo.


Algo que distorsiona este valor, es que debido a que el valor y la saturación también son parte del valor de entrada, el tono puede estar muy alejado de los puntos "completamente rojo / verde / azul", pero dado que los valores máximo y mínimo están muy cerca, la diferencia acabamos de calcular es todavía muy pequeño. Afortunadamente, esto es fácil de solucionar dividiendo la diferencia entre la diferencia entre el componente más grande y el más pequeño del color de entrada que calculamos anteriormente.


Con esas modificaciones obtenemos un valor de 0 si los colores que no son el color más grande son los mismos, también conocido como el tono es rojo / verde / azul o un valor de -1/1 si es amarillo / magenta / cian y un valor en el medio de los otros tonos.


Al agregar un valor basado en el tono del componente de entrada más intenso, reasignamos los colores a -1 a 1 para los colores rojizos, 1 a 3 para los colores verdosos y 3 a 5 para los colores azulados.


La división luego tira de esto en el rango de -1/6 a 5/6 y tomando la parte fraccional de eso hace que los valores negativos se envuelvan, por lo que está en el rango de 0 a 1 como se esperaba.


Obtener la saturación y el valor es más fácil. La saturación es la diferencia entre el componente más grande y el más pequeño, dividido por el componente más grande. La división factoriza la multiplicación por el valor que hacemos en la conversión de hsv a rgb. Para obtener el valor, podemos tomar el componente más grande del valor de entrada, ya que ni aplicar el tono ni la saturación puede hacer que el valor más alto caiga por debajo de 1, por lo que todo lo que entra depende del valor del color.


float3 rgb2hsv(float3 rgb)
{
    float maxComponent = max(rgb.r, max(rgb.g, rgb.b));
    float minComponent = min(rgb.r, min(rgb.g, rgb.b));
    float diff = maxComponent - minComponent;
    float hue = 0;
    if(maxComponent == rgb.r) {
        hue = 0+(rgb.g-rgb.b)/diff;
    } else if(maxComponent == rgb.g) {
        hue = 2+(rgb.b-rgb.r)/diff;
    } else if(maxComponent == rgb.b) {
        hue = 4+(rgb.r-rgb.g)/diff;
    }
    hue = frac(hue / 6);
    float saturation = diff / maxComponent;
    float value = maxComponent;
    return float3(hue, saturation, value);
}

Una vez hecho esto, ahora puede convertir un color en hsv, ajustarlo y volver a moverlo a rgb para representar el color.La más fácil es agregar algún valor al tono para que cambie en un efecto de arco iris.


Mientras que con el tono puede simplemente agregar valores donde un cambio de 1 resulta en el mismo tono nuevamente, 0.5 es el tono opuesto, etc, la saturación y el valor generalmente deben mantenerse entre 0 y 1. Para ajustarlos, podemos usar el operador de potencia.


Tomar la N potencia de la saturación o valor donde N está por encima de 1 hace que el color sea menos saturado / más oscuro. Tomando el N poder con N entre 0 y 1 hace que el color sea más saturado / brillante.


Con este conocimiento, podemos hacer un sombreador que ajuste esas propiedades en el sombreador.


Sin embargo, es importante tener en cuenta que no debe hacer eso solo para ajustar estáticamente una imagen, ya que las conversiones, así como tomar el poder de un número, son operaciones bastante caras, en cambio considere cambiar la imagen en un programa de manipulación de imágenes.


La función de fragmento de un sombreador que ajusta todos los componentes del color HSV podría verse así.


Shader "Mobile Friendly/Image FX/HueSaturationValue" {
Properties {
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    _HueAmount("Hue Amount", Range(0,1)) = 0.5
    _SaturationAmount("Saturation Amount", Range(0,1)) = 0.5
    _ValueAmount("Value Amount", Range(0,1)) = 0.5
    }
SubShader {
    Pass{
    CGPROGRAM
    #pragma vertex vert_img
    #pragma fragment frag
    #pragma fragmentoption ARB_precision_hint_fastest
    #include "UnityCG.cginc"
    #include "IFX.cginc"
    
    uniform sampler2D _MainTex;
    fixed _HueAmount, _SaturationAmount,_ValueAmount;
    
    fixed4 frag(v2f_img i) : COLOR {
    	fixed4 source = tex2D(_MainTex, i.uv);
    	fixed3 hsv = rgb2hsv(source);
    	hsv.x += _HueAmount;
    	hsv.y += _SaturationAmount;//pow(hsv.y, );
    	hsv.z += _ValueAmount;//pow(hsv.z, );
    	source = fixed4(hsv2rgb(hsv),1);
    	return source;
    	}
    ENDCG
    }
}
FallBack "Diffuse"
}

Por ahora dejemoslo ahí, este tutorial quedó demasiado largo y bastante enredado, espero que no se haga un embrollo la cabeza, yo tampoco soy el matemático salido de Harvard para explicar este tema. Como siempre espero que te haya sido de utilidad y te espero para el próximo blog. hasta otra

201 visualizaciones0 comentarios

Entradas recientes

Ver todo

Comments


bottom of page