Grayscale image determines which of two colors is used

I am looking for a shader that does the following:

  • There are two color inputs and one grayscale texture input.
  • Let’s say the two colors are red and blue. The black areas of the texture show up as red, the white areas show up as blue, and the shades of gray in between will be various degrees of magenta.

This seems like a basic enough shader, but my complete lack of experience with shaders has meant that I have no idea how to build it (or even the best search terms to find it, if someone else has). Can anyone give me any direction?

EDIT: After more searching, finding someone’s shader, and adding random lines of code hoping I knew what I was doing, I ended up with this:

Shader "Custom/VertexLitAlphaColoring" {
Properties {
    _FirstColor ("First Color", Color) = (0, 0, 0, 1)
    _SecondColor ("Second Color", Color) = (1, 1, 1, 1)
    _MainTex ("Base (RGB)", 2D) = "white" {}
}

    SubShader {
       Tags { "RenderType"="Opaque" }
       LOD 80

       Pass {
         Tags { "LightMode" = "Vertex" }

         // Setup Basic
         Material {
          Diffuse (1,1,1,1)
          Ambient (1,1,1,1)
         } 
         Lighting On
         // Lerp between AlphaColor and the basic vertex lighting color
         SetTexture [_MainTex] {
            constantColor [_SecondColor]
          		combine previous * constant
         }
         
         SetTexture [_MainTex] {
          	constantColor [_FirstColor] 
          		//combine previous lerp(texture) constant DOUBLE, previous lerp(texture) constant
          		combine previous lerp(texture) constant DOUBLE
         }
            
       }

    }

}

It works pretty well, so long as the texture is by an alpha channel. Anyone have a better one? I’d prefer having one that actually works with the black and white of the image, rather than the alpha.

EDIT #2: Never mind… while it does work in certain instances, it’s not the right solution. I’ll keep looking… but any help is appreciated.

it seems it will only work texture that has alpha channel ,

Yes, I said that at the bottom. I’d like to get a better one that creates the proper results.

I ended up using Strumpy, and it now works perfectly. It’s probably way too complicated – like all Strumpy shaders seem to be – but it does the job:

Shader "Custom/TwoTone"
{
	Properties 
	{
_color1("_color1", Color) = (1,0,0,1)
_MainTexture("_MainTexture", 2D) = "black" {}
_color2("_color2", Color) = (1,0.6923077,0,1)

	}
	
	SubShader 
	{
		Tags
		{
"Queue"="Geometry"
"IgnoreProjector"="False"
"RenderType"="Opaque"

		}

		
Cull Back
ZWrite On
ZTest LEqual
ColorMask RGBA
Fog{
}


		CGPROGRAM
#pragma surface surf BlinnPhongEditor  vertex:vert
#pragma target 2.0


float4 _color1;
sampler2D _MainTexture;
float4 _color2;

			struct EditorSurfaceOutput {
				half3 Albedo;
				half3 Normal;
				half3 Emission;
				half3 Gloss;
				half Specular;
				half Alpha;
				half4 Custom;
			};
			
			inline half4 LightingBlinnPhongEditor_PrePass (EditorSurfaceOutput s, half4 light)
			{
float4 Multiply1=float4( s.Albedo.x, s.Albedo.y, s.Albedo.z, 1.0 ) * light;
float4 Splat0=light.w;
float4 Multiply0=float4( s.Gloss.x, s.Gloss.y, s.Gloss.z, 1.0 ) * Splat0;
float4 Multiply2=light * Multiply0;
float4 Add2=Multiply1 + Multiply2;
float4 Mask1=float4(Add2.x,Add2.y,Add2.z,0.0);
float4 Luminance1= Luminance( Multiply0.xyz ).xxxx;
float4 Add0=Luminance1 + float4( s.Albedo.x, s.Albedo.y, s.Albedo.z, 1.0 );
float4 Mask0=float4(0.0,0.0,0.0,Add0.w);
float4 Add1=Mask1 + Mask0;
return Add1;

			}

			inline half4 LightingBlinnPhongEditor (EditorSurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
			{
				half3 h = normalize (lightDir + viewDir);
				
				half diff = max (0, dot ( lightDir, s.Normal ));
				
				float nh = max (0, dot (s.Normal, h));
				float spec = pow (nh, s.Specular*128.0);
				
				half4 res;
				res.rgb = _LightColor0.rgb * diff;
				res.w = spec * Luminance (_LightColor0.rgb);
				res *= atten * 2.0;

				return LightingBlinnPhongEditor_PrePass( s, res );
			}
			
			struct Input {
				float2 uv_MainTexture;

			};

			void vert (inout appdata_full v, out Input o) {
float4 VertexOutputMaster0_0_NoInput = float4(0,0,0,0);
float4 VertexOutputMaster0_1_NoInput = float4(0,0,0,0);
float4 VertexOutputMaster0_2_NoInput = float4(0,0,0,0);
float4 VertexOutputMaster0_3_NoInput = float4(0,0,0,0);


			}
			

			void surf (Input IN, inout EditorSurfaceOutput o) {
				o.Normal = float3(0.0,0.0,1.0);
				o.Alpha = 1.0;
				o.Albedo = 0.0;
				o.Emission = 0.0;
				o.Gloss = 0.0;
				o.Specular = 0.0;
				o.Custom = 0.0;
				
float4 Sampled2D0=tex2D(_MainTexture,IN.uv_MainTexture.xy);
float4 Multiply0=_color1 * Sampled2D0;
float4 Sampled2D1=tex2D(_MainTexture,IN.uv_MainTexture.xy);
float4 Invert0= float4(1.0, 1.0, 1.0, 1.0) - Sampled2D1;
float4 Multiply2=_color2 * Invert0;
float4 Add0=Multiply0 + Multiply2;
float4 Master0_1_NoInput = float4(0,0,1,1);
float4 Master0_2_NoInput = float4(0,0,0,0);
float4 Master0_3_NoInput = float4(0,0,0,0);
float4 Master0_4_NoInput = float4(0,0,0,0);
float4 Master0_5_NoInput = float4(1,1,1,1);
float4 Master0_7_NoInput = float4(0,0,0,0);
float4 Master0_6_NoInput = float4(1,1,1,1);
o.Albedo = Add0;

				o.Normal = normalize(o.Normal);
			}
		ENDCG
	}
	Fallback "Diffuse"
}

The far-too-complicated Strumpy shader was causing big issues when I tried to create multiple of the same object, so I’ve gone back to basic Unity shaders, and created this:

Shader "Custom/TwoTone" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Color1 ("Main Color", Color) = (0, 0, 0, 1)
		_Color2 ("Detail Color", Color) = (1, 1, 1, 1)
	}
	SubShader {
        Pass {
            Material {
                Diffuse [_Color1]
                Ambient [_Color1]
            }
            Lighting On
            SeparateSpecular On
            SetTexture [_MainTex] {
            	constantColor (0, 0, 0, 1)
            	combine constant lerp (texture) primary DOUBLE
            	//combine primary lerp(texture) constant 
            }
            
            SetTexture [_MainTex] {
            	constantColor [_Color2]
            	combine constant lerp (texture) primary
            }
            
        }
    }
	FallBack "Diffuse"
}

The only problem, besides the fact that it uses the alpha channel instead of RGB, is that the second color doesn’t follow regular lighting… it acts as though its self-illuminated (and I suppose it is, because the color is a constant). Anyone have an idea how to fix that?

For that matter, is anyone actually reading this?

I’m reading but frankly, I’ve also other work to do and cannot always afford to help users who hope to get along without learning anything about shader programming. Sorry. Maybe you find someone with more time at their disposal.

Well, given that I wrote the above shader myself, and made the previous shader in Strumpy, it’s a little strange to say that I’m not learning anything about shader programming. But thanks for the acknowledgement, such as it is.

Sorry for my wording. I exaggerated. However, I have to admit that I don’t consider configuring a fixed-function shader with ShaderLab as shader programming (I’m old enough to remember the difference between a fixed-function and a programmable graphics pipeline), neither do I think that using Strumpy is the same as shader programming. I’m happy to help with links to references about shader programming if you should one day decide that you want to give it a try.

Well, I’m done with working for today. If I understood your problem correctly, this should solve it:

Shader "myshader" {
	Properties {
	    _FirstColor ("First Color", Color) = (0, 0, 0, 1)
	    _SecondColor ("Second Color", Color) = (1, 1, 1, 1)
	    _MainTex ("Base (RGB)", 2D) = "white" {}	
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Lambert

        half4 _FirstColor;
        half4 _SecondColor;
		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Emission = lerp(_FirstColor, _SecondColor, c.r);

			// alternatively (with diffuse lighting): 
			// o.Albedo = lerp(_FirstColor, _SecondColor, c.r);
		}
		ENDCG
	} 
	FallBack "Diffuse"
}

(I’m using the red channel assuming that it is a grayscale image.)

Thank you for the shader! The o.Albedo version was exactly what I needed!

Yes, please. While the docs contain references to terms like struct, half4, and sampler, I haven’t yet found a good way to understand how they all work together, or even what they mean. (However, now that I have access to a shader where the end result is exactly what I was hoping for, I might be able to make some headway.)

You probably want to learn Surface Shaders. There are some tutorials in Unity Gems: http://unitygems.com/noobshader1/ and some video tutorials in Unity Cookie: http://cgcookie.com/unity/series/introduction-to-surface-shader-scripting/ .
I’ve written a wikibook about the shading language Cg (which is used in Surface Shaders): Cg Programming - Wikibooks, open books for an open world ; that book is specifically about vertex and fragment shaders in Unity: Cg Programming/Unity - Wikibooks, open books for an open world

I’m currently absorbing the Unity Gems noobshader link. It’s perfect, and has already revealed a few basic things that I wish I’d known earlier (and is quite clearly showing that what I was doing earlier was NOT shader programming, but just basic ShaderLab stuff). Thank you again!