# Hue, saturation, brightness, contrast shader

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

1. ### 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. {
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

Joined:
Aug 19, 2012
Posts:
99
And how I supposed to use this? 3. ### 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. {
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. }
Code (CSharp):
2. {
3.     Properties
4.     {
5.         _MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {}
6.     }
7.
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.
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
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.

PrimalCoder, UnityLighting and twobob like this.
4. ### 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..

http://www.graficaobscura.com/matrix/

EvilGremlin likes this.
5. ### 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

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

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

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 9. ### 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

Joined:
Jan 13, 2015
Posts:
1
Very useful! A lot of thanks!

11. ### 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

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

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.

### Guest

useful! tnx for sharing Andrey!

15. ### canis

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

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

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

18. ### 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):
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.     }
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.             {
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

Joined:
Aug 17, 2015
Posts:
43
Thank you so much guys, it works perfectly!  unityunity