Overlay Blend Mode Shader

Alright, I’m not very familiar with Shaders but I’ll try to explain in details what I’m trying to achieve, why, and what I achieved so far.

The background

I use NGUI for my Game GUI and one great feature is that it keeps all images from your GUI on the same Atlas. This is good for saving both space and drawcalls I guess.

Okay, when using NGUI I use this Unlit Transparent Colored Shader, which let me pick a color and multiply with my texture, this is great when I have a white sprite and want to change it’s color.

Here is the Shader Code (I hope there’s no problem sharing it here as it’s the base for my other shader):

Shader "Unlit/Transparent Colored"
{
    Properties
    {
        _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
    }
    
    SubShader
    {
        LOD 100

        Tags
        {
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }
        
        Pass
        {
            Cull Off
            Lighting Off
            ZWrite Off
            Fog { Mode Off }
            Offset -1, -1
            ColorMask RGB
            AlphaTest Greater .01
            Blend SrcAlpha OneMinusSrcAlpha
            ColorMaterial AmbientAndDiffuse
            
            SetTexture [_MainTex]
            {
                Combine Texture * Primary
            }
        }
    }
}

The goal

What I want to achieve is to have a monochromatic GUI that can change color based on the color I pick. In other words, I want to achieve a effect similar to the Overlay Blend Mode on Photoshop.

Quoted from: The Overlay Blend Mode In Photoshop

Using Images to explain that would be that the gray button bellow would change its color as I picked blue(0, 0, 255) or red(192,43,43).

What’s achieved so far?

By changing the shader above (and I had no Idea what I was doing) I could achieve a similar effect, that works quite well. Just change the previous shader Pass so it looks like this:

        Pass
        {
            Cull Off
            Lighting Off
            ZWrite Off
            Fog { Mode Off }
            Offset -1, -1
            ColorMask RGB
            AlphaTest Greater .01
            Blend SrcAlpha OneMinusSrcAlpha
            ColorMaterial AmbientAndDiffuse
            
            SetTexture [_MainTex]
            {
                Combine Texture +- Primary, Texture * Primary
            }
            
            SetTexture [_MainTex]
            {
                Combine Texture +- previous, Texture * Primary
            }
        }

The problem

This shader works pretty well when I have grayscale buttons, the 50% gray looks about the color I pick and the “shadows and lights” seem to adjust well to the selected color.

But when I put a colored sprite with this shader… well… I excpected that by selecting the 50% gray as my pick color everything should remain unchanged compared to the original sprite, but that’s not what happen. Check the results bellow:

Original / 50% gray picked

1238335--53524--$btntwitterx.png

Because of that weird effect I had separate all my grayscale and colored sprites into 2 atlases, which causes me a lot of trouble with rendring order.

The Request

Can anyone help me achieve what I want with the right code so I could use the same atlas for colored and grayscale sprites? I mean, because obviously my Pass is not doing the Overlay Blend Mode right, it’s just something similar.

The Thanks

Thanks in advance, specially if you had patience to read this big post. I hope everything is well explained.

I have found some more info about the Overlay Formula, as I quoted before it combines Multiply with Screen blend modes.

Wikipedia: Blend modes - Wikipedia
StackOverflow: ios - Overlay blend mode formula? - Stack Overflow

It seems I’ll need an IF statement based on the texture pixel color… is there a code to check that in the SetTexture of a shader so I could chose between Screen and Multiply?

Also, how can I use Screen Blend Mode? Multiply seems to be as easy as Combine Texture * Primary

If I understand correctly, you want what is described in this thread.

Here are all those bits of code put together:

Shader "Custom/OverlayBlend" 
{
	Properties {
		_MainTex ("Texture", 2D) = "" {}
		_Color ("Blend Color", Color) = (0.2, 0.3, 1 ,1)
	} 
 
	SubShader {
 
		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
		
		Lighting Off 
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite Off 
		Fog { Mode Off } 
		
		Pass {  
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
 
			#include "UnityCG.cginc"
 
			struct appdata_t {
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
			};
 
			struct v2f {
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
			};
 
			sampler2D _MainTex;
 
			uniform float4 _MainTex_ST;
			uniform float4 _Color;
			
			v2f vert (appdata_t v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.color = v.color;
				o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
				return o;
			}
 
			fixed4 frag (v2f i) : COLOR
			{
				// Get the raw texture value
				float4 texColor = tex2D(_MainTex, i.texcoord);
				// Calculate the twiceLuminance of the texture color
				float twiceLuminance =  dot(texColor, fixed4(0.2126, 0.7152, 0.0722, 0)) * 2;
				// Declare the output structure
				
				fixed4 output = 0;
				
				// The actual Overlay/High Light method is based on the shader
				if (twiceLuminance < 1) {
					output = lerp(fixed4(0, 0, 0, 0), _Color, twiceLuminance);
				} else {
					output = lerp(_Color, fixed4(1, 1, 1, 1), twiceLuminance - 1);
				}
				
				// The alpha can actually just be a simple blend of the two-
				// makes things nicely controllable in both texture and color
				output.a  = texColor.a * _Color.a;
				return output;
			}
			ENDCG 
		}
	}   
 
	Fallback off 
}
2 Likes

No Daniel, it didn’t work, I guess the problem described in that thread is not exactly the same as what I want here. I’m still trying to find the right shader, maybe I’ll have to use some CG code for that, right?

You’ll definitely have to use Cg. Perhaps you can explain in more detail what you would like your possible inputs to be, and what output you expect in each case? I can’t view any of your original attachments except the twitter logo.

Sorry for that, I updated the post with new images so I hope it works now.

In adition now that I studied NGUI a little more I discovered what its color pick does, it does pixel coloring on the plane where the sprite is going to be rendered with the selected color. So basically the code I want is:

foreach texture pixel
{
  if (texture.pixel is darker than 0.5)
  {
    finalPixelColor = baseColor MULTIPLY texture.pixel.color
  }
  else
  {
    finalPixelColor = baseColor SCREEN texture.pixel.color
  }
}

Sorry, I’m not familiar with CG script…

Still looking for a solution without success, can someone take a look on this or give me directions on what to study?

Up we go, still need some help here.

Had a need for overlay a while back. However i made the mistake of trying to do the overlay on each channel RGB separately, didn’t think to use luminescence for it. Anyway with that in mind, made a few changes to it and came up with something that seems to work. However not tested on any mobile device, I’ve read that using if tests on shaders isn’t the best idea, and there may well be better ways to approximate the effect with less cost.

Either way, here’s what i came up with for it.

Note: Not really knowing about luminescence at all not sure what the best value is for checking it. I found checking it < 1 looked a little bit off, checking <0 might also be useful (but without opening photoshop i cant compare the effect exactly) but 0.5 seems to give a decent effect as far as my memory of what an overlay does. However you may want to modify the value you check luminescence against if you want to fine tune.

Shader "Custom/OverlayBlend"
{
    Properties 
	{
        _MainTex ("Texture", 2D) = "" {}
        _Color ("Blend Color", Color) = (0.5, 0.5, 0.5, 1.0)
    } 	

    SubShader 
	{
		Tags 
		{
			"Queue" = "Transparent"
			"RenderType" = "Transparent" 
		}        

        Lighting Off
        Blend SrcAlpha OneMinusSrcAlpha
		     
        Pass 
		{  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest 

            #include "UnityCG.cginc" 

            struct appdata_custom 
			{
                float4 vertex : POSITION;
                fixed2 uv : TEXCOORD0;
            };

            struct v2f
			{
                float4 vertex : POSITION;
                fixed2 uv : TEXCOORD0;
            };
			
            sampler2D _MainTex;
            fixed4 _MainTex_ST;
            fixed4 _Color;            

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

            fixed4 frag (v2f i) : COLOR
            {
                fixed4 diffuse = tex2D(_MainTex, i.uv);
                fixed luminance =  dot(diffuse, fixed4(0.2126, 0.7152, 0.0722, 0));
				fixed oldAlpha = diffuse.a;

                if (luminance < 0.5) 
                    diffuse *= 2 * _Color;
				else 
					diffuse = 1-2*(1-diffuse)*(1-_Color);

                diffuse.a  = oldAlpha * _Color.a;
                return diffuse;
            }
			ENDCG 
        }
    }   
    Fallback off 
}

Thanks a lot, this shader is almost what I need, I just need to know 2 things:

  1. Could you tell if it is going to run on mobile devices? If yes is it optimized enough for that?

  2. How can I use the mesh vertex color instead of the variable _Color of the shader?

Okay, with very few modifications I got it to work exactly like I wanted. Very special thanks to HenryStrattonFW.

Shader "Custom/OverlayBlend"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "" {}
	}
	
	SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"RenderType" = "Transparent"
		}
		
		Lighting Off
		Blend SrcAlpha OneMinusSrcAlpha
		
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			
			#include "UnityCG.cginc"
			
			struct appdata_custom
			{
				float4 vertex : POSITION;
				fixed2 uv : TEXCOORD0;
				float4 color : COLOR;
			};
			
			struct v2f
			{
				float4 vertex : POSITION;
				fixed2 uv : TEXCOORD0;
				float4 color : COLOR;
			};
			
			sampler2D _MainTex;
			fixed4 _MainTex_ST;
			
			v2f vert (appdata_custom v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = TRANSFORM_TEX(v.uv,_MainTex);
				o.color = v.color;
				return o;
			}          
			
			fixed4 frag (v2f i) : COLOR
			{
				fixed4 diffuse = tex2D(_MainTex, i.uv);
				fixed luminance =  dot(diffuse, fixed4(0.2126, 0.7152, 0.0722, 0));
				fixed oldAlpha = diffuse.a;
				
				if (luminance < 0.5)
					diffuse *= 2 * i.color;
				else
					diffuse = 1-2*(1-diffuse)*(1-i.color);
				
				diffuse.a  = oldAlpha * i.color.a;
				return diffuse;
			}
			ENDCG
		}
	}
	Fallback off
}
2 Likes

Hi. I’ve just had to implement a overlay shader my self and came across this thread when I was looking for inspiration. Cool you got what you wanted Lax! But even though you aren’t looking any more I think I’ll post my solution as it might be helpful for others trying to do the same.

My shader is more like the overlay mode in photoshop as it operates on the whole screen area. The algorithm is a direct implementation of the algorithm described on the wikipedia page you linked to in an earlier post. (Blend modes - Wikipedia)

Anyway here goes (some explanation can be found after the code):

Shader "Custom/Overlay" {
    Properties {
        _OverlayTex ("Overlay (RGB)", 2D) = "white" {}
    }
    
    SubShader {
        Tags { "RenderType"="Transparent" "Queue"="Transparent+1000"}
        LOD 200
        
        GrabPass {}
        
        Pass {
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            
            sampler2D _GrabTexture;
            sampler2D _OverlayTex;
            
            struct appdata_t {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 projPos : TEXCOORD1;
            };
            
            float4 _OverlayTex_ST;
            
            v2f vert( appdata_t v ){
                v2f o;
                o.vertex = mul( UNITY_MATRIX_MVP, v.vertex );
                o.uv = TRANSFORM_TEX( v.texcoord, _OverlayTex );
                o.projPos = ComputeScreenPos( o.vertex );
                return o;
            }
            
            half4 frag( v2f i ) : COLOR {
                i.projPos /= i.projPos.w;
                half4 base = tex2D( _GrabTexture, float2( i.projPos.xy ));
                half4 overlay = tex2D( _OverlayTex, i.uv );

                return lerp(   1 - 2 * (1 - base) * (1 - overlay),    2 * base * overlay,    step( base, 0.5 ));
            }
            
            ENDCG
        }
    }
}

As it can be seen above I use a pass to grab what is currently on the screen before rendering the overlay. This is a feature provided by Unity simply by including the GrabPass { } and it stores a reference to the grabbed texture in _GrabTexture. I can now use this as my base layer for the blend and I use normalized screen coordinates as uv’s when reading from it (that is what the "o.projPos = ComputeScreenPos( o.vertex ); " and “i.projPos /= i.projPos.w;” lines are for). Finally I combine the two texture colors basted on the algorithm from the wikipedia page. The lerp and step functions is just another way of doing an if statement, This is hopefully less cost expensive than an actual if ( I’m by no means a shader expert! )

I’ve also made a shorter version implemented as a surface shader. But it is probably not that useful since it reacts to lighting. (In my experience the color of the pixel is alway change at least a little when using surface shaders. Even though you define your own lighting model that ignores light! )

Anyway, that’s it. Hope somebody finds this help full.

Cheers

  • Jakob
2 Likes

Yeah Jakob , liking the magic of your implementation of the overlay logic in a simple lerp…

Wish I could find similar implementations of this set: http://forum.unity3d.com/threads/121661-Free-Photoshop-Blends - or some idea how you go from those if statements to what you produced.

Either way, thanks for sharing!

I used a plane and put it between the camera and the scene with this shader. the color looks right but it renders the scene upside down:face_with_spiral_eyes:
edit:
nvm, my screen coordinate in unity is reversed that’s why the output is upside down.

Whoa, nice to see this post is still going on. I’ll check that implementation later on on my project as it seems to be a bit more efficient.

Been a long time since I saw this thread, nice to see some alternative suggestions. @Ball-E 's use a lerp and steps is a good way around the if test. Will have to remember that next time I need to dig up my old overlay shader.

Hi,
With unity5 the original shader in this thread is no longer usable.

I tried this from Lex

But I need the extra saturation from the original shader.

this is the original shader:

Shader "Unlit/Transparent Colored"
{
    Properties
    {
        _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
    }
  
    SubShader
    {
        LOD 100
        Tags
        {
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }
      
        Pass
        {
            Cull Off
            Lighting Off
            ZWrite Off
            Fog { Mode Off }
            Offset -1, -1
            ColorMask RGB
            AlphaTest Greater .01
            Blend SrcAlpha OneMinusSrcAlpha
            ColorMaterial AmbientAndDiffuse
          
            SetTexture [_MainTex]
            {
                Combine Texture +- Primary, Texture * Primary
            }
          
            SetTexture [_MainTex]
            {
                Combine Texture +- previous, Texture * Primary
            }
        }
    }
}

How can I achieve the same effect in unity5?

Thank you

So, as Spiral12 correctly stated, almost none of this works with unity 5 anymore.

Ball-E’s last offer was the closest thing I got to a working shader, and after minor adjustments I got it to work in a GUI context.
The transparent shaders in the custom overlay image were still rendered and affecting the image, though.

The shader has therefore been modified to take transparency into account so custom images are correctly blended in, as well as introduced a property which allows you to modify the strength of the overall layer.

The last thing missing (to follow the photoshop effect) would be to make sure that overlays with a light intensity of 0.5 are rendered transparent, right now it just defaults to screen.

Shader "Custom/Overlay" {
    Properties{
        _MainTex("Texture", 2D) = "" {}
        _str("Overlay Strength", Float) = 0.45
    }

    SubShader{
        Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }

        GrabPass{}

        Pass{
            CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

            sampler2D _GrabTexture;
            sampler2D _MainTex;
            float _str;

            struct appdata_t {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 projPos : TEXCOORD1;
            };

            float4 _MainTex_ST;

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

            half4 frag(v2f i) : COLOR{
                i.projPos /= i.projPos.w;
                half4 base = tex2D(_GrabTexture, float2(i.projPos.x, 1-i.projPos.y));
                half gray = (base.r + base.g + base.b) / 3;
                half4 overlay = tex2D(_MainTex, i.uv);

                float4 effect = lerp(1 - (2 * (1 - base)) * (1 - overlay), (2 * base) * overlay, step(gray, 0.5f));
              
                return lerp(base, effect, (overlay.w * _str));
            }

            ENDCG
        }
    }
}

I made a little change at line 51.

step(gray, 0.5f) → step(base, 0.5f)

No idea what I’ve done but it looks exactly same as PS.

Shader "Custom/Overlay" {
    Properties{
        _MainTex("Texture", 2D) = "" {}
        _str("Overlay Strength", Float) = 0.45
    }

    SubShader{
        Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }

        GrabPass{}

        Pass{
            CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

            sampler2D _GrabTexture;
            sampler2D _MainTex;
            float _str;

            struct appdata_t {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 projPos : TEXCOORD1;
            };

            float4 _MainTex_ST;

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

            half4 frag(v2f i) : COLOR{
                i.projPos /= i.projPos.w;
                half4 base = tex2D(_GrabTexture, float2(i.projPos.x, 1-i.projPos.y));
                half gray = (base.r + base.g + base.b) / 3;
                half4 overlay = tex2D(_MainTex, i.uv);

                float4 effect = lerp(1 - (2 * (1 - base)) * (1 - overlay), (2 * base) * overlay, step(base, 0.5f));
            
                return lerp(base, effect, (overlay.w * _str));
            }

            ENDCG
        }
    }
}

It must be a noob question, but I have the same issue about it rendering upside down, how do you reversed the coordinates in the shader? If I play the scene on PC it renders the right way, but on mobile (and in the “Scene View”) it renders upside down. I know it is because de coordinates are handled different between platforms, but like I’ve said I’m a shader noob, I down’t know how make it render correctly in different platforms.