Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Hue, saturation, brightness, contrast shader

Discussion in 'Shaders' started by Andrey-Postelzhuk, Aug 5, 2014.

  1. Andrey-Postelzhuk

    Andrey-Postelzhuk

    Joined:
    Nov 26, 2013
    Posts:
    75
    I spent a lot of time on this shader. I hope it can be useful for someone else. There is my final version of the functions:

    Code (CSharp):
    1. inline float3 applyHue(float3 aColor, float aHue)
    2. {
    3.     float angle = radians(aHue);
    4.     float3 k = float3(0.57735, 0.57735, 0.57735);
    5.     float cosAngle = cos(angle);
    6.     //Rodrigues' rotation formula
    7.     return aColor * cosAngle + cross(k, aColor) * sin(angle) + k * dot(k, aColor) * (1 - cosAngle);
    8. }
    9.  
    10.  
    11. inline float4 applyHSBEffect(float4 startColor, fixed4 hsbc)
    12. {
    13.     float _Hue = 360 * hsbc.r;
    14.     float _Brightness = hsbc.g * 2 - 1;
    15.     float _Contrast = hsbc.b * 2;
    16.     float _Saturation = hsbc.a * 2;
    17.  
    18.     float4 outputColor = startColor;
    19.     outputColor.rgb = applyHue(outputColor.rgb, _Hue);
    20.     outputColor.rgb = (outputColor.rgb - 0.5f) * (_Contrast) + 0.5f;
    21.     outputColor.rgb = outputColor.rgb + _Brightness;        
    22.     float3 intensity = dot(outputColor.rgb, float3(0.299,0.587,0.114));
    23.     outputColor.rgb = lerp(intensity, outputColor.rgb, _Saturation);
    24.  
    25.     return outputColor;
    26. }
    The main problem was with the hue. Hue shift is a 3D-vector rotation. Creating quaternion and converting them to the matrix is much heavier than Rodrigues rotation formula.
    Use this shader and be happy.
     
  2. domkia

    domkia

    Joined:
    Aug 19, 2012
    Posts:
    99
    And how I supposed to use this? :)
     
  3. Andrey-Postelzhuk

    Andrey-Postelzhuk

    Joined:
    Nov 26, 2013
    Posts:
    75
    There are just shader functions. I use this effect for NGUI sprites. Sprite color (tint) is used for passing Hue, brightness, contrast, saturation. But this is my situation. Functions can be used in any other shader.
    Here is an example:

    HSB.cginc file:
    Code (CSharp):
    1.  
    2. inline float3 applyHue(float3 aColor, float aHue)
    3. {
    4.     float angle = radians(aHue);
    5.     float3 k = float3(0.57735, 0.57735, 0.57735);
    6.     float cosAngle = cos(angle);
    7.     //Rodrigues' rotation formula
    8.     return aColor * cosAngle + cross(k, aColor) * sin(angle) + k * dot(k, aColor) * (1 - cosAngle);
    9. }
    10.  
    11.  
    12. inline float4 applyHSBEffect(float4 startColor, fixed4 hsbc)
    13. {
    14.     float _Hue = 360 * hsbc.r;
    15.     float _Brightness = hsbc.g * 2 - 1;
    16.     float _Contrast = hsbc.b * 2;
    17.     float _Saturation = hsbc.a * 2;
    18.  
    19.     float4 outputColor = startColor;
    20.     outputColor.rgb = applyHue(outputColor.rgb, _Hue);
    21.     outputColor.rgb = (outputColor.rgb - 0.5f) * (_Contrast) + 0.5f;
    22.     outputColor.rgb = outputColor.rgb + _Brightness;      
    23.     float3 intensity = dot(outputColor.rgb, float3(0.299,0.587,0.114));
    24.     outputColor.rgb = lerp(intensity, outputColor.rgb, _Saturation);
    25.  
    26.     return outputColor;
    27. }
    Full shader code. Unlit - Transparent HSB.shader file:
    Code (CSharp):
    1. Shader "Unlit/Transparent HSB"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {}
    6.     }
    7.  
    8.     SubShader
    9.     {
    10.         LOD 100
    11.  
    12.         Tags
    13.         {
    14.             "Queue" = "Transparent"
    15.             "IgnoreProjector" = "True"
    16.             "RenderType" = "Transparent"
    17.         }
    18.      
    19.         Cull Off
    20.         Lighting Off
    21.         ZWrite Off
    22.         Fog { Mode Off }
    23.         Offset -1, -1
    24.         Blend SrcAlpha OneMinusSrcAlpha
    25.  
    26.         Pass
    27.         {
    28.             CGPROGRAM
    29.             #pragma vertex vert
    30.             #pragma fragment frag
    31.              
    32.             #include "UnityCG.cginc"
    33.             #include "HSB.cginc"
    34.  
    35.             struct appdata_t
    36.             {
    37.                 float4 vertex : POSITION;
    38.                 float2 texcoord : TEXCOORD0;
    39.                 fixed4 color : COLOR;
    40.             };
    41.  
    42.             struct v2f
    43.             {
    44.                 float4 vertex : SV_POSITION;
    45.                 half2 texcoord : TEXCOORD0;
    46.                 fixed4 color : COLOR;
    47.             };
    48.  
    49.             sampler2D _MainTex;
    50.          
    51.             v2f vert (appdata_t v)
    52.             {
    53.                 v2f o;
    54.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    55.                 o.texcoord = v.texcoord;
    56.                 o.color = v.color;
    57.                 return o;
    58.             }
    59.              
    60.             fixed4 frag (v2f i) : COLOR
    61.             {
    62.                 float4 startColor = tex2D(_MainTex, i.texcoord);
    63.                 float4 hsbColor = applyHSBEffect(startColor, i.color);
    64.                 return hsbColor;
    65.             }
    66.             ENDCG
    67.         }
    68.     }
    69.  
    70.     SubShader
    71.     {
    72.         LOD 100
    73.  
    74.         Tags
    75.         {
    76.             "Queue" = "Transparent"
    77.             "IgnoreProjector" = "True"
    78.             "RenderType" = "Transparent"
    79.         }
    80.      
    81.         Pass
    82.         {
    83.             Cull Off
    84.             Lighting Off
    85.             ZWrite Off
    86.             Fog { Mode Off }
    87.             Offset -1, -1
    88.             ColorMask RGB
    89.             //AlphaTest Greater .01
    90.             Blend SrcAlpha OneMinusSrcAlpha
    91.             ColorMaterial AmbientAndDiffuse
    92.          
    93.             SetTexture [_MainTex]
    94.             {
    95.                 Combine Texture * Primary
    96.             }
    97.         }
    98.     }
    99. }
    100.  
     
  4. sschaem

    sschaem

    Joined:
    Feb 14, 2014
    Posts:
    148
    Note: you can do all that using a single operation.
    Build a 3x3 matrix (color transform) and apply it to your vec3 RGB color.

    colorRGB *= _Transform3x3;

    The matrix can concatenate any number of operation in any order.
    And is not llimited hue (luminance preserving or not), levels, contrast, saturation, etc..

    This article is over 20 years old now, but you can find a good base implementation.
    http://www.graficaobscura.com/matrix/
     
    EvilGremlin likes this.
  5. Andrey-Postelzhuk

    Andrey-Postelzhuk

    Joined:
    Nov 26, 2013
    Posts:
    75
    Matrix applying is one operation. But creation of this matrix is not. I used matrix for hue transformation. Creation of transform matrix had too many operations. I converted quaternion to matrix. Shader didn't work on some android devices.
     
  6. sschaem

    sschaem

    Joined:
    Feb 14, 2014
    Posts:
    148
    Yes, but the good part is that building the matrix is done outside the pixel shader.
    This saves a tremendous amount of pixel shader computation. Build matrix once, apply to all pixel.

    The code require in the shader should be this :

    fixed4 frag (v2f i): COLOR
    {
    return mul(_Transform3x3, tex2D(_MainTex, i.texcoord).rgb);
    }
     
    EvilGremlin likes this.
  7. Andrey-Postelzhuk

    Andrey-Postelzhuk

    Joined:
    Nov 26, 2013
    Posts:
    75
    You are right, sschaem. I'll try to implement this for NGUI sprites. It should be a good challenge. It's much easier to use already existed tint color for passing hsb parameters. But easiest is not always the best.
     
  8. metaleap

    metaleap

    Joined:
    Oct 3, 2012
    Posts:
    589
    Quite the interesting link, thanks! This in particular was illuminating under "Converting to Luminance":

    Seems like UnityCG.cginc OTOH uses:

    Code (csharp):
    1.  
    2. // Converts color to luminance (grayscale)
    3. inline fixed Luminance( fixed3 c )
    4. {
    5.     return dot( c, fixed3(0.22, 0.707, 0.071) );
    6. }
    7.  
    Now I can't decide what to make of this :D
     
  9. bricevdm

    bricevdm

    Joined:
    Nov 4, 2009
    Posts:
    34
    Thanks for sharing Andrey! Brilliant :)

    I did a contrast shader a while back that had the advantage of being less destructive. You current solution should clamp very quickly. A simple workaround is to apply a "S" curve like you would in photoshop, with a simple remap of the range and a power 3 (val*val*val), then to modulate the intensity (at the time I used the lerp() like you did for saturation).

    best regards
     
  10. radya

    radya

    Joined:
    Jan 13, 2015
    Posts:
    1
    Very useful! A lot of thanks!
     
  11. Ben-BearFish

    Ben-BearFish

    Joined:
    Sep 6, 2011
    Posts:
    1,204
    @Andrey Postelzhuk Would this shader be able to solve the contrast degradation problem I'm trying to recreate in this post?
     
  12. Andrey-Postelzhuk

    Andrey-Postelzhuk

    Joined:
    Nov 26, 2013
    Posts:
    75
    @Ben BearFish I don't know what a 'contrast degradation problem' is. I just optimized common HSB shader.
     
    imrankhanswati likes this.
  13. Ben-BearFish

    Ben-BearFish

    Joined:
    Sep 6, 2011
    Posts:
    1,204
    @Andrey Postelzhuk Contrast degradation is when the contrast of an image/surface becomes darker the greater the viewing angle is from the center of the object.

    So if you're viewing a tv straight on you'll see 100% contrast, but as you move to an angle let's say a 45 degree angle, then the contrast drops (degrades) to 50%. I guess really I need a shader that changes the contrast of the colors on the surface of an object based on the camera viewing angle.
     
  14. Deleted User

    Deleted User

    Guest

    useful! tnx for sharing Andrey!
     
  15. canis

    canis

    Joined:
    Oct 25, 2013
    Posts:
    79
    UnityLighting and mcbauer like this.
  16. 5c4r3cr0w

    5c4r3cr0w

    Joined:
    Mar 8, 2016
    Posts:
    35
    Thanks a lot for this. Works like a charm :)
     
  17. Dexit33

    Dexit33

    Joined:
    Mar 21, 2015
    Posts:
    1
    It keeps black and coloring white, any way to swap it?
     
  18. MattijsKneppers

    MattijsKneppers

    Joined:
    Jun 30, 2014
    Posts:
    15
    Thanks for this Andrey,

    Here is a slightly modified version that works for me when I directly copy and paste it into a new .shader file

    Code (CSharp):
    1. Shader "Unlit/ColorAdjust"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Hue ("Hue", Range(-360, 360)) = 0.
    7.         _Brightness ("Brightness", Range(-1, 1)) = 0.
    8.         _Contrast("Contrast", Range(0, 2)) = 1
    9.         _Saturation("Saturation", Range(0, 2)) = 1
    10.     }
    11.     SubShader
    12.     {
    13.         Tags { "RenderType"="Opaque" }
    14.         LOD 100
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #include "UnityCG.cginc"
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 float2 uv : TEXCOORD0;
    25.             };
    26.             struct v2f
    27.             {
    28.                 float2 uv : TEXCOORD0;
    29.                 float4 vertex : SV_POSITION;
    30.             };
    31.             sampler2D _MainTex;
    32.             float4 _MainTex_ST;
    33.             float _Hue;
    34.             float _Brightness;
    35.             float _Contrast;
    36.             float _Saturation;
    37.             v2f vert (appdata v)
    38.             {
    39.                 v2f o;
    40.                 o.vertex = UnityObjectToClipPos(v.vertex);
    41.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    42.                 return o;
    43.             }
    44.             inline float3 applyHue(float3 aColor, float aHue)
    45.             {
    46.                 float angle = radians(aHue);
    47.                 float3 k = float3(0.57735, 0.57735, 0.57735);
    48.                 float cosAngle = cos(angle);
    49.                 //Rodrigues' rotation formula
    50.                 return aColor * cosAngle + cross(k, aColor) * sin(angle) + k * dot(k, aColor) * (1 - cosAngle);
    51.             }
    52.             inline float4 applyHSBEffect(float4 startColor)
    53.             {
    54.                 float4 outputColor = startColor;
    55.                 outputColor.rgb = applyHue(outputColor.rgb, _Hue);
    56.                 outputColor.rgb = (outputColor.rgb - 0.5f) * (_Contrast)+0.5f;
    57.                 outputColor.rgb = outputColor.rgb + _Brightness;
    58.                 float3 intensity = dot(outputColor.rgb, float3(0.299, 0.587, 0.114));
    59.                 outputColor.rgb = lerp(intensity, outputColor.rgb, _Saturation);
    60.                 return outputColor;
    61.             }
    62.             fixed4 frag (v2f i) : SV_Target
    63.             {
    64.                 float4 startColor = tex2D(_MainTex, i.uv);
    65.                 float4 hsbColor = applyHSBEffect(startColor);
    66.                 return hsbColor;
    67.             }
    68.             ENDCG
    69.         }
    70.     }
    71. }
     
  19. DavidJayIndie

    DavidJayIndie

    Joined:
    Aug 17, 2015
    Posts:
    70
    Thank you so much guys, it works perfectly! :D
     
  20. KarlKarl2000

    KarlKarl2000

    Joined:
    Jan 25, 2016
    Posts:
    606
    Saved my bacon. Much thanks you guys. Better than the latest default URP post processing.
     
  21. absurdnoize

    absurdnoize

    Joined:
    Nov 23, 2019
    Posts:
    10
    Doesn't work on Android
     
  22. MG_Benni

    MG_Benni

    Joined:
    May 16, 2017
    Posts:
    3
    Hey, it works good, but if the pixels have the same rgb values applyHue doesn't work. The object is still grey. Else its working. But I need to have same rgb values like: aColor.r = 0.6, aColor.g = 0.6, aColor.b = 0.6. Does anyone know what's the problem?