Blend colors only for an area of a sprite

I’m trying to create a robust tutorial system for a mobile game where a semitransparent overlay appears on the whole screen and the interactible items are highlighted (not affected by the overlay). I have created a simplified scene to illustrate what I am trying to achieve.

This is how it looks with the overlay off
overlay off.png

And with the overlay on
overlay on.png

If I want to highlight an object that is not obstructed by other objects in the scene, I can just change the sortingLayer of the sprite and it will be on top of the overlay. However, this does not work with objects that are obstructed by other items (see below).

actual behaviour.png

What I would like to happen in this particular example is for the monster and it’s arms (blue and green rectangles) to be highlighted, but only the part above the red rectangle (see below - bring in front only the part with the yellow rectangle highlight).

desired behaviour.png

I also want the solution for this to be reusable for other similar situations and to not have to resort to using sprite masks that match perfectly the object which I am trying to highlight. I think this problem can be solved with shaders and alpha blending, but I’m not sure what the solution would be.

My idea is to write a shader that would be put on the monster material that would act something like this “If you have the tutorial transparent overlay on top of you, do not blend the colors, use only the original colors, but if you have other objects in front of you, blend the colors”.

Is it possible to solve it this way, or are there better ways of handling this?




Found a solution using stencil buffers!

solution.png

What I ended up doing was using 3 shaders with stencil buffers added to them. One for the monster (green and blue rectangles), one for the counter (red rectangle) and one for the overlay. The key here is to set the render queues properly so the monster parts are drawn first, then the counter, and finally the overlay. For the monster I write a value into the buffer (e.g. 2). For the counter I write another value in the buffer (1) and in the overlay I render only the pixels when the stencil buffer is value is different than the one assigned to the monster.

Shader for the object that has to be highlighted (green and blue rectangles in this case):

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Monster/BlendMonsterTest" {
     Properties {
         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
         _Color ("Color", Color) = (1,1,1,1)
     }
     SubShader {
          Tags{ "Queue" = "Transparent"
             "RenderType" = "Transparent" }
            Blend SrcAlpha OneMinusSrcAlpha
            Stencil {
                Ref 2
                Comp Always
                Pass replace
            }
         Pass {
         CGPROGRAM
// Upgrade NOTE: excluded shader from DX11, OpenGL ES 2.0 because it uses unsized arrays
#pragma exclude_renderers d3d11 gles
         #pragma vertex vert
         #pragma fragment frag
         #include "UnityCG.cginc"

      
         uniform fixed4 _Color;
         uniform sampler2D _MainTex;

         struct appdata_t {
         float4 vertex   : POSITION;
         float4 color    : COLOR;
         float2 texcoord : TEXCOORD0;
         };
         struct v2f {
             float4 pos : SV_POSITION;
             float2 uv : TEXCOORD0;
             float4 color : COLOR;
         };
         v2f vert (appdata_t v) {
             v2f o;
             o.pos = UnityObjectToClipPos (v.vertex);
             o.uv = v.texcoord;
             o.color = v.color;
             return o;
         }
         fixed4 frag (v2f i) : COLOR {
            fixed4 textureColor = tex2D(_MainTex, i.uv) * _Color;
            clip(textureColor.a - 0.05);
            return textureColor;
         }
         ENDCG
         }
     }
}

Shader for the object(s) that blocks the highlighted object:

  • same as previous one except for the stencil
Stencil {
         Ref 1
         Comp Always
          Pass replace
}

Shader for the overlay:

*same as the previous one except for the stencil

Stencil {
     Ref 2
     Comp NotEqual
}