Problem with getting hue shift shader right.

For one of my projects I need to hue shift some elements.

I figured out that this shader could help me. I manage to hue shift between colors but I loose my transparency in my sprite atlas.

anybody an idea how to turn this on?

tx

Shader "HSB_HSV_Colorpicker" {
  Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _HueShift("HueShift", Float) = 0
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      #pragma target 3.0
         
        float3 rgb_to_hsv_no_clip(float3 RGB)
        {
                float3 HSV;
           
         float minChannel, maxChannel;
         if (RGB.x > RGB.y) {
          maxChannel = RGB.x;
          minChannel = RGB.y;
         }
         else {
          maxChannel = RGB.y;
          minChannel = RGB.x;
         }
         
         if (RGB.z > maxChannel) maxChannel = RGB.z;
         if (RGB.z < minChannel) minChannel = RGB.z;
           
                HSV.xy = 0;
                HSV.z = maxChannel;
                float delta = maxChannel - minChannel;             //Delta RGB value
                if (delta != 0) {                    // If gray, leave H  S at zero
                   HSV.y = delta / HSV.z;
                   float3 delRGB;
                   delRGB = (HSV.zzz - RGB + 3*delta) / (6.0*delta);
                   if      ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
                   else if ( RGB.y == HSV.z ) HSV.x = ( 1.0/3.0) + delRGB.x - delRGB.z;
                   else if ( RGB.z == HSV.z ) HSV.x = ( 2.0/3.0) + delRGB.y - delRGB.x;
                }
                return (HSV);
        }

        float3 hsv_to_rgb(float3 HSV)
        {
                float3 RGB = HSV.z;
           
                   float var_h = HSV.x * 6;
                   float var_i = floor(var_h);   // Or ... var_i = floor( var_h )
                   float var_1 = HSV.z * (1.0 - HSV.y);
                   float var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
                   float var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
                   if      (var_i == 0) { RGB = float3(HSV.z, var_3, var_1); }
                   else if (var_i == 1) { RGB = float3(var_2, HSV.z, var_1); }
                   else if (var_i == 2) { RGB = float3(var_1, HSV.z, var_3); }
                   else if (var_i == 3) { RGB = float3(var_1, var_2, HSV.z); }
                   else if (var_i == 4) { RGB = float3(var_3, var_1, HSV.z); }
                   else                 { RGB = float3(HSV.z, var_1, var_2); }
           
           return (RGB);
        }

      struct Input {
          float2 uv_MainTex;
      };
     
      sampler2D _MainTex;
      float _HueShift;
     
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
         
          float3 hsv = rgb_to_hsv_no_clip(o.Albedo.xyz);
          hsv.x+=_HueShift;
                 
          if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
          o.Albedo = half3(hsv_to_rgb(hsv));
      }
     
      ENDCG
    }
    Fallback "Diffuse"
  }

Below the line;

o.Albedo = half3(hsv_to_rgb(hsv));

add

o.Alpha = tex2D (_MainTex, IN.uv_MainTex).a;

I notice no difference when adding that extra line.

Oh, you might need to change
Tags { ā€œRenderTypeā€ = ā€œOpaqueā€ }
to
Tags { ā€œRenderTypeā€ = ā€œTransparentā€ }

you want to change the surface shader line to have alpha.#pragma surface surf Lambert alpha
also change the rendertype to ā€œTransparentā€.

Great it works. Thank you all.

below the hue shift shader where hue and saturation can be changed.

Shader "HSB_HSV_Colorpicker" {
  Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _HueShift("HueShift", Float) = 0
      _SaturationShift("SaturationShift", Float) = 1.0
    }
    SubShader {
      Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent" }
      ZWrite Off
      Blend SrcAlpha OneMinusSrcAlpha
      Cull Off
      CGPROGRAM
      //#pragma surface surf Lambert
      #pragma surface surf Lambert alpha
      #pragma target 3.0
         
        float3 rgb_to_hsv_no_clip(float3 RGB)
        {
                float3 HSV;
           
         float minChannel, maxChannel;
         if (RGB.x > RGB.y) {
          maxChannel = RGB.x;
          minChannel = RGB.y;
         }
         else {
          maxChannel = RGB.y;
          minChannel = RGB.x;
         }
         
         if (RGB.z > maxChannel) maxChannel = RGB.z;
         if (RGB.z < minChannel) minChannel = RGB.z;
           
                HSV.xy = 0;
                HSV.z = maxChannel;
                float delta = maxChannel - minChannel;             //Delta RGB value
                if (delta != 0) {                    // If gray, leave H  S at zero
                   HSV.y = delta / HSV.z;
                   float3 delRGB;
                   delRGB = (HSV.zzz - RGB + 3*delta) / (6.0*delta);
                   if      ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
                   else if ( RGB.y == HSV.z ) HSV.x = ( 1.0/3.0) + delRGB.x - delRGB.z;
                   else if ( RGB.z == HSV.z ) HSV.x = ( 2.0/3.0) + delRGB.y - delRGB.x;
                }
                return (HSV);
        }

        float3 hsv_to_rgb(float3 HSV)
        {
                float3 RGB = HSV.z;
           
                   float var_h = HSV.x * 6;
                   float var_i = floor(var_h);   // Or ... var_i = floor( var_h )
                   float var_1 = HSV.z * (1.0 - HSV.y);
                   float var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
                   float var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
                   if      (var_i == 0) { RGB = float3(HSV.z, var_3, var_1); }
                   else if (var_i == 1) { RGB = float3(var_2, HSV.z, var_1); }
                   else if (var_i == 2) { RGB = float3(var_1, HSV.z, var_3); }
                   else if (var_i == 3) { RGB = float3(var_1, var_2, HSV.z); }
                   else if (var_i == 4) { RGB = float3(var_3, var_1, HSV.z); }
                   else                 { RGB = float3(HSV.z, var_1, var_2); }
           
           return (RGB);
        }

      struct Input {
          float2 uv_MainTex;
      };
     
      sampler2D _MainTex;
      float _HueShift;
      float _SaturationShift;
     
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
         
          float3 hsv = rgb_to_hsv_no_clip(o.Albedo.xyz);
          hsv.x+=_HueShift;
          hsv.y = _SaturationShift;
          
          if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
          o.Albedo = half3(hsv_to_rgb(hsv));
          o.Alpha = tex2D (_MainTex, IN.uv_MainTex).a;
      }
     
      ENDCG
    }
    Fallback "Diffuse"
  }

Iā€™ve just tried using the shader, but Iā€™m still getting some weird glitches with certain pixels. I have white or almost white pixels which are displayed as red, and a rim of pixels around them which are rendered with various strange colours. This happens even when the HueShift variable is set to 0, so Iā€™m guessing there is some kind of round-off error occurring.

Hereā€™s an example of the problem:

Any idea where the problem might be? Perhaps I can do something with the image to prevent this?

EDIT: Perhaps it has something to do with the platform Iā€™m running it on, since my major version of OpenGL reports 2, but this is compiled for 3.0

EDIT2: This seems to be a likely source of the issue. I ported the code to a direct resource edit (using Texture2Dā€™s SetPixels for OpenGL ES 1.1 compatibility) and the red eye-syndrome doesnā€™t happen. There still are some strange glitches with the borders of the eye, however.

EDIT3: My bad, I ported it wrong, which caused a beneficial bug but the hue shifting didnā€™t occur properly anymore. Both issues still occur, but I can minimize the border glitches by checking ā€œdelta > 0.05ā€ (or something similar) instead of exactly checking ā€œdelta != 0ā€

The problem appears to be the saturation shift.

If you input a grayscale color into the functions, then changing the HSV.y variable to 1 (for full saturation), will result in the G B channels of the output color to become 0. Thatā€™s obviously because var_i is 0, and in that case, var_1 = HVS.z * ( 1 - 1 ) = 0, the same happens with var_3.

This is why the white eye became red. Since R = HSV.z (original color), G B = 0.

Soā€¦ the saturation feature is nice, but I donā€™t use it so Iā€™ve removed it, and the shader works perfectly now. However, Iā€™d recommend looking into fixing the problem as well :stuck_out_tongue:

EDIT: Easy fix. multiply by the saturation instead of straight setting it.

Sorry to hi-jack the post, but Iā€™m trying to get this shader to work with OpenGL ES 2.0

Iā€™ve already refactored the shader to fit within the 64 arithmetic instruction limit (it was previously using a lot of useless if else statements to save on performance I guess, which prevented it from compiling at all), and converted it to a vertex/fragment shader, as opposed to purely a surface shader. Itā€™s a bit messy atm, since Iā€™ve never made any shaders like this before in Unity.

Hereā€™s the code I have right now, which compiles and runs fine in emulated ES 2.0, but on the device the color doesnā€™t change (which makes me think itā€™s using the fall-back)

Any ideas?

The only thing I can think of is possibly the divide by 0 which may occur when delta == 0 (but this doesnā€™t seem to cause any issues when I test it in unity), and I tried adding a small value (0.01f) to the delta at all times so it wouldnā€™t divide by zero. No luck thereā€¦

Shader "HSB_HSV_Colorpicker" {
  Properties {
  	  _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
      _MainTex ("Texture", 2D) = "white" {}
      _HueShift("HueShift", Range(0,1) ) = 0
      _Sat("Saturation", Range(0, 1)) = 0
    }
    SubShader {
    
    	
      Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent" }
      ZWrite Off
      Blend SrcAlpha OneMinusSrcAlpha
      Cull Off
      
    Pass
    {
      CGPROGRAM
	  #pragma vertex vert
      #pragma fragment surf Lambert alpha
      #pragma target 2.0
      
      #include "UnityCG.cginc"
         
	struct v2f {
		float4  pos : SV_POSITION;
		float2  uv : TEXCOORD0;
	};

	float4 _MainTex_ST;
         
	v2f vert (appdata_base v)
	{
	    v2f o;
	    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
	    o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
	    return o;
	}
	 
	float3 rgb_to_hsv_no_clip(float3 RGB)
	{
		float3 HSV = 0;
		
		float minChannel, maxChannel;
		
		maxChannel = max(RGB.x, RGB.y);
		minChannel = min(RGB.x, RGB.y);
		
		maxChannel = max(RGB.z, maxChannel);
		minChannel = min(RGB.z, minChannel);
		
		HSV.z = maxChannel;
		
		float delta = maxChannel - minChannel;             //Delta RGB value
		
		//if ( delta > 0 ) 
		//{                    // If gray, leave H  S at zero
			HSV.y = delta / HSV.z;
			float3 delRGB = (HSV.zzz - RGB + 3*delta) / (6*delta);
			if ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
			if ( RGB.y == HSV.z ) HSV.x = ( 1.0 / 3.0 ) + delRGB.x - delRGB.z;
			if ( RGB.z == HSV.z ) HSV.x = ( 2.0 / 3.0 ) + delRGB.y - delRGB.x;
		//}
		
		return (HSV);
	}
	
	float3 hsv_to_rgb(float3 HSV)
	{
		float var_h = HSV.x * 6;
		//float var_i = floor(var_h);   // Or ... var_i = floor( var_h )
		float var_1 = HSV.z * ( 1.0 - HSV.y );
		float var_2 = HSV.z * ( 1.0 - HSV.y * (var_h-floor( var_h )));
		float var_3 = HSV.z * ( 1.0 - HSV.y * (1-(var_h-floor( var_h ))));
		
		float3 RGB = float3(HSV.z, var_1, var_2);
		
		if (var_h < 5) 	{ RGB = float3(var_3, var_1, HSV.z); }
		if (var_h < 4) 	{ RGB = float3(var_1, var_2, HSV.z); }
		if (var_h < 3) 	{ RGB = float3(var_1, HSV.z, var_3); }
		if (var_h < 2) 	{ RGB = float3(var_2, HSV.z, var_1); }
		if (var_h < 1) 	{ RGB = float3(HSV.z, var_3, var_1); }
	    
		return (RGB);
	}

	struct Input 
	{
	  float2 uv_MainTex;
	};
	
	sampler2D _MainTex;
	float _HueShift;
	float _Sat;
	
	half4 surf (Input IN) : COLOR
	{
	  half4 col = tex2D (_MainTex, IN.uv_MainTex);
	 
	  float3 hsv = rgb_to_hsv_no_clip(col.xyz);
	  hsv.x += _HueShift;
	  //hsv.y *= _Sat;
	  
	  if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
	  return half4( half3(hsv_to_rgb(hsv) ), col.a);
	}
     
      ENDCG
    }
    }
    Fallback "Particles/Alpha Blended"
  }

Just ran into this:

Any idea what that means?

This looks like itā€™s getting a bit confused - itā€™s sort of half surface shader, half standard shader.

Youā€™ve got multiple input structures and all sorts going on. Itā€™s a wonder it runs at all :stuck_out_tongue:

Iā€™ve ripped out all the surface shader stuff in there and left you with the same code but in standard vertex/fragment format. Seems to run ok here.

Shader "HSB_HSV_Colorpicker" {
	Properties {
		_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
		_MainTex ("Texture", 2D) = "white" {}
		_HueShift("HueShift", Range(0,1) ) = 0
		_Sat("Saturation", Range(0, 1)) = 0
	}
	SubShader {

		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent" }
		ZWrite Off
		Blend SrcAlpha OneMinusSrcAlpha
		Cull Off

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"

			float3 rgb_to_hsv_no_clip(float3 RGB)
			{
				float3 HSV = 0;

				float minChannel, maxChannel;

				maxChannel = max(RGB.x, RGB.y);
				minChannel = min(RGB.x, RGB.y);

				maxChannel = max(RGB.z, maxChannel);
				minChannel = min(RGB.z, minChannel);

				HSV.z = maxChannel;

				float delta = maxChannel - minChannel;             //Delta RGB value

				//if ( delta > 0 ) 
				//{                    // If gray, leave H  S at zero
					HSV.y = delta / HSV.z;
					float3 delRGB = (HSV.zzz - RGB + 3*delta) / (6*delta);
					if ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
					if ( RGB.y == HSV.z ) HSV.x = ( 1.0 / 3.0 ) + delRGB.x - delRGB.z;
					if ( RGB.z == HSV.z ) HSV.x = ( 2.0 / 3.0 ) + delRGB.y - delRGB.x;
				//}

				return (HSV);
			}

			float3 hsv_to_rgb(float3 HSV)
			{
				float var_h = HSV.x * 6;
				//float var_i = floor(var_h);   // Or ... var_i = floor( var_h )
				float var_1 = HSV.z * ( 1.0 - HSV.y );
				float var_2 = HSV.z * ( 1.0 - HSV.y * (var_h-floor( var_h )));
				float var_3 = HSV.z * ( 1.0 - HSV.y * (1-(var_h-floor( var_h ))));

				float3 RGB = float3(HSV.z, var_1, var_2);

				if (var_h < 5) 	{ RGB = float3(var_3, var_1, HSV.z); }
				if (var_h < 4) 	{ RGB = float3(var_1, var_2, HSV.z); }
				if (var_h < 3) 	{ RGB = float3(var_1, HSV.z, var_3); }
				if (var_h < 2) 	{ RGB = float3(var_2, HSV.z, var_1); }
				if (var_h < 1) 	{ RGB = float3(HSV.z, var_3, var_1); }

				return (RGB);
			}

			struct v2f {
				float4	pos : SV_POSITION;
				float2	uv : TEXCOORD0;
			};

			float4 _MainTex_ST;

			v2f vert (appdata_base v)
			{
				v2f o;
				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			sampler2D _MainTex;
			float _HueShift;
			float _Sat;

			half4 frag(v2f i) : COLOR
			{
				half4 col = tex2D(_MainTex, i.uv);

				float3 hsv = rgb_to_hsv_no_clip(col.xyz);
				hsv.x += _HueShift;
				//hsv.y *= _Sat;

				if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
				return half4( half3(hsv_to_rgb(hsv) ), col.a);
			}

			ENDCG
		}
	}
	Fallback "Particles/Alpha Blended"
}
1 Like

Found the issue already. I forget to add the " : TEXCOORD" macro after the fragment shaderā€™s input struct.

Hereā€™s the complete, working code for an OpenGL ES 2.0 (3Gs, 4G, iPad, iPod 4g) compatible hue shift shader:

Shader "HSB_HSV_Colorpicker" {
  Properties {
  	  _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
      _MainTex ("Texture", 2D) = "white" {}
      _HueShift("HueShift", Range(0,1) ) = 0
      _Sat("Saturation", Range(0, 1)) = 0
    }
    SubShader {
    
    	
      Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent" }
      ZWrite Off
      Blend SrcAlpha OneMinusSrcAlpha
      Cull Off
      
    Pass
    {
      CGPROGRAM
	  #pragma vertex vert
      #pragma fragment surf Lambert alpha
      #pragma target 2.0
      
      #include "UnityCG.cginc"
         
	struct v2f {
		float4  pos : SV_POSITION;
		float2  uv : TEXCOORD;
	};

	float4 _MainTex_ST;
         
	v2f vert (appdata_base v)
	{
	    v2f o = (v2f)0;
	    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
	    o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
	    return o;
	}
	 
	float3 rgb_to_hsv_no_clip(float3 RGB)
	{
		float3 HSV = 0;
		
		float minChannel, maxChannel;
		
		maxChannel = max(RGB.x, RGB.y);
		minChannel = min(RGB.x, RGB.y);
		
		maxChannel = max(RGB.z, maxChannel);
		minChannel = min(RGB.z, minChannel);
		
		HSV.z = maxChannel;
		
		float delta = maxChannel - minChannel;    //Delta RGB value
		
		HSV.y = delta / HSV.z;
		float3 delRGB = (HSV.zzz - RGB + 3*delta) / (6*delta);
		if ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
		if ( RGB.y == HSV.z ) HSV.x = ( 1.0 / 3.0 ) + delRGB.x - delRGB.z;
		if ( RGB.z == HSV.z ) HSV.x = ( 2.0 / 3.0 ) + delRGB.y - delRGB.x;
		
		return (HSV);
	}
	
	float3 hsv_to_rgb(float3 HSV)
	{
		float var_h = HSV.x * 6;
		
		float var_1 = HSV.z * ( 1.0 - HSV.y );
		float var_2 = HSV.z * ( 1.0 - HSV.y * (var_h-floor( var_h )));
		float var_3 = HSV.z * ( 1.0 - HSV.y * (1-(var_h-floor( var_h ))));
		
		float3 RGB = float3(HSV.z, var_1, var_2);
		
		if (var_h < 5) 	{ RGB = float3(var_3, var_1, HSV.z); }
		if (var_h < 4) 	{ RGB = float3(var_1, var_2, HSV.z); }
		if (var_h < 3) 	{ RGB = float3(var_1, HSV.z, var_3); }
		if (var_h < 2) 	{ RGB = float3(var_2, HSV.z, var_1); }
		if (var_h < 1) 	{ RGB = float3(HSV.z, var_3, var_1); }
	    
		return (RGB);
	}

	struct Input 
	{
	  float2 uv_MainTex : TEXCOORD;
	};
	
	sampler2D _MainTex;
	float _HueShift;
	float _Sat;
	
	half4 surf (Input IN) : COLOR
	{
	  half4 col = tex2D (_MainTex, IN.uv_MainTex);
	 
	  float3 hsv = rgb_to_hsv_no_clip(col.xyz);
	  hsv.x += _HueShift;
	  //hsv.y *= _Sat;
	  
	  if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
	  return half4( half3(hsv_to_rgb(hsv) ), col.a);
	}
     
      ENDCG
    }
    }
    Fallback "Particles/Alpha Blended"
  }

Currently the saturation is still commented, since it wouldnā€™t compile otherwise (exceeds 64 arithmetic operations)

I was in need of a hsv shader as well and found the sources above very helpful. However, I found that the solutions above still contain some glitches and so I went on and implemented this matrix based solution here: Code: Affine HSV color manipulation

So here is the shader then: (please note that the hue shift expects input in the range of [0,360] and value and saturation parameters are acting as multipliers)

Shader "Custom/HSVShader" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_HueShift("HueShift", Float ) = 0
		_Sat("Saturation", Float) = 1
		_Val("Value", Float) = 1
	}
	SubShader {

		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent" }
		ZWrite Off
		Blend SrcAlpha OneMinusSrcAlpha
		Cull Off

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"

			float3 shift_col(float3 RGB, float3 shift)
			{
			float3 RESULT = float3(RGB);
			float VSU = shift.z*shift.y*cos(shift.x*3.14159265/180);
    			float VSW = shift.z*shift.y*sin(shift.x*3.14159265/180);
    			
    			RESULT.x = (.299*shift.z+.701*VSU+.168*VSW)*RGB.x
    					+ (.587*shift.z-.587*VSU+.330*VSW)*RGB.y
    		 			+ (.114*shift.z-.114*VSU-.497*VSW)*RGB.z;
    			
    			RESULT.y = (.299*shift.z-.299*VSU-.328*VSW)*RGB.x
    					+ (.587*shift.z+.413*VSU+.035*VSW)*RGB.y
        				+ (.114*shift.z-.114*VSU+.292*VSW)*RGB.z;
    			
    			RESULT.z = (.299*shift.z-.3*VSU+1.25*VSW)*RGB.x
        				+ (.587*shift.z-.588*VSU-1.05*VSW)*RGB.y
        				+ (.114*shift.z+.886*VSU-.203*VSW)*RGB.z;
    			
			return (RESULT);
			}

			struct v2f {
				float4	pos : SV_POSITION;
				float2	uv : TEXCOORD0;
			};

			float4 _MainTex_ST;

			v2f vert (appdata_base v)
			{
				v2f o;
				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			sampler2D _MainTex;
			float _HueShift;
			float _Sat;
			float _Val;

			half4 frag(v2f i) : COLOR
			{
				half4 col = tex2D(_MainTex, i.uv);
				float3 shift = float3(_HueShift, _Sat, _Val);
				
				return half4( half3(shift_col(col, shift)), col.a);
			}
			ENDCG
		}
	}
	Fallback "Particles/Alpha Blended"
}
1 Like

@DtodaB: I need hue shader too and found your shader the best one!

I donā€™t have access to mac and iphone now and canā€™t test this shader.
Will it work correctly on 3gs and above? Can anyone test it please?

Thanks

I needed a similar shader for palette shifting some parts of a vehicle and not others (things like wheels and lights that need to stay the same for all vehicles). I wound up adding a bit to DtodaBā€™s code, and thought others might find it useful for character customization and so on, so here you go:

Shader "Custom/LayeredHSVShift"
{
Properties {
    _Color ("Color", Color) = (1,1,1,1)
    _MainTex ("Color to Shift (RGB) Trans (A)", 2D) = "white" {}
  
    _HueShift ("Hue (0-360)", Float) = 0
    _SatShift ("Saturation", Float) = 1
    _ValShift ("Value (brightness)", Float) = 1
  
    _Tex2 ("Unshifted Color (RGB) Trans (A)", 2D) = "white" {}
  
    _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
  
}

SubShader {
    Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
    LOD 200
  
CGPROGRAM
#pragma surface surf Lambert alphatest:_Cutoff
#pragma target 3.0

sampler2D _MainTex;
sampler2D _Tex2;

float _HueShift;
float _SatShift;
float _ValShift;

fixed4 _Color;

float3 shift_col(float3 RGB, float3 shift)
            {
            float3 RESULT = float3(RGB);
            float VSU = shift.z*shift.y*cos(shift.x*3.14159265/180);
                float VSW = shift.z*shift.y*sin(shift.x*3.14159265/180);
             
                RESULT.x = (.299*shift.z+.701*VSU+.168*VSW)*RGB.x
                        + (.587*shift.z-.587*VSU+.330*VSW)*RGB.y
                        + (.114*shift.z-.114*VSU-.497*VSW)*RGB.z;
             
                RESULT.y = (.299*shift.z-.299*VSU-.328*VSW)*RGB.x
                        + (.587*shift.z+.413*VSU+.035*VSW)*RGB.y
                        + (.114*shift.z-.114*VSU+.292*VSW)*RGB.z;
             
                RESULT.z = (.299*shift.z-.3*VSU+1.25*VSW)*RGB.x
                        + (.587*shift.z-.588*VSU-1.05*VSW)*RGB.y
                        + (.114*shift.z+.886*VSU-.203*VSW)*RGB.z;
             
            return (RESULT);
            }


struct Input {
    float2 uv_Tex2;
    float2 uv_MainTex;
};

void surf (Input IN, inout SurfaceOutput o)
{
    fixed4 b = tex2D(_Tex2, IN.uv_Tex2) * _Color;
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
  
    float3 hsv;
    hsv.x  = _HueShift;
    hsv.y  = _SatShift;
    hsv.z  = _ValShift;
  
    c.rgb = shift_col(c.rgb, hsv);

    o.Albedo = b.rgb * b.a + c.rbg * c.a * (1-b.a);
    o.Alpha = c.a;

}
ENDCG
}

Fallback "Transparent/Cutout/VertexLit"
}
1 Like


Just released Texture Adjustments asset including various texture tweaking tools, including hue shift, saturation,contrast, levels control, etc. Plugin comes with shaders, editor tool and runt-time API.

VacuumShaders - Facebook Twitter YouTube

10 years later I find this thread as the first result when looking for hue shift shadersā€¦ and, standing the test of time, @anon_37480891 solution works almost perfectly for what I was looking for, which is using this on sprite renderers so I can have only one background texture and recolor it without using multiple huge texturesā€¦ the one thing I had to change on the shader is that SpriteRenderers use their color to adjust vertex color which the original shader does not consider, therefore if you change the alpha of the color, or a canvas group alpha, this shader would still be solid.

The change was literally just creating an in struct that receives color, and passing on to the pixel shader, so basically nothing. Here is the updated version to be use hue shift with sprite renderers and canvas groups:

Shader "Custom/HSVShader" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _HueShift("HueShift", Float ) = 0
        _Sat("Saturation", Float) = 1
        _Val("Value", Float) = 1
    }
    SubShader {

        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent" }
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
           


            float3 shift_col(float3 RGB, float3 shift)
            {
            float3 RESULT = float3(RGB);
            float VSU = shift.z*shift.y*cos(shift.x*3.14159265/180);
                float VSW = shift.z*shift.y*sin(shift.x*3.14159265/180);
               
                RESULT.x = (.299*shift.z+.701*VSU+.168*VSW)*RGB.x
                        + (.587*shift.z-.587*VSU+.330*VSW)*RGB.y
                        + (.114*shift.z-.114*VSU-.497*VSW)*RGB.z;
               
                RESULT.y = (.299*shift.z-.299*VSU-.328*VSW)*RGB.x
                        + (.587*shift.z+.413*VSU+.035*VSW)*RGB.y
                        + (.114*shift.z-.114*VSU+.292*VSW)*RGB.z;
               
                RESULT.z = (.299*shift.z-.3*VSU+1.25*VSW)*RGB.x
                        + (.587*shift.z-.588*VSU-1.05*VSW)*RGB.y
                        + (.114*shift.z+.886*VSU-.203*VSW)*RGB.z;
               
            return (RESULT);
            }

            struct vertex_data
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
                float4  col : COLOR;
            };
           
            float4 _MainTex_ST;
           
            v2f vert (vertex_data v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.col = v.color;
                return o;
            }

            sampler2D _MainTex;
            float _HueShift;
            float _Sat;
            float _Val;

            half4 frag(v2f i) : COLOR
            {
                half4 col = tex2D(_MainTex, i.uv) * i.col;
                float3 shift = float3(_HueShift, _Sat, _Val);
               
                return half4( half3(shift_col(col, shift)), col.a);
            }
            ENDCG
        }
    }
    Fallback "Particles/Alpha Blended"
}
1 Like

Thank you!

This is most welcome/essential/cool!