Stencil Shader no longer working with Lightweight Render Pipeline

I wanted to use the LWRP in my project which I successfully upgraded to 2019.2.0f1. I follow the documentation on integrating the LWRP into the project and all works well (updating shaders and materials) except for my custom stencil shader. I have to admit I do not understand very well what is happening here - I copied the shader from this source and managed to adjust it so that I can give color values to the shader by code.


What it does

This is the shader in action - the yellow area has a “occluder” material and the circled “occluded” (shader code down below). The effect of part of the circle color changing when entering the yellow area is the effect that I want. In addition when the circle collides with the yellow area I set the target color I want with code on the yellow-area-object.


The Problem

When switching my graphics setting to use LWRP the occluded part (stencil ref 4) gets cut off but the second pass of the occluded shader simply does not trigger. I tried switching the passes around and now the occluded part works but the un-occluded one does not, so it only executes the first pass. I tried for hours and cannot find out why that is. When I remove LWRP from my graphics settings the shader works as intended.


The Code

This is the shader code for the “occluder”:

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

Shader "Sprites/Occluder"
{
  Properties
  {
     [PerRendererData] _MainTex ( "Sprite Texture", 2D ) = "white" {}
      _Color ( "Tint", Color ) = ( 1, 1, 1, 1 )
     [MaterialToggle] PixelSnap ( "Pixel snap", Float ) = 0
      _AlphaCutoff ( "Alpha Cutoff", Range( 0.01, 1.0 ) ) = 0.1
  }


  SubShader
  {
      Tags
      {
          "Queue" = "Transparent"
          "IgnoreProjector" = "True"
          "RenderType" = "TransparentCutout"
          "PreviewType" = "Plane"
          "CanUseSpriteAtlas" = "True"
      }

      Cull Off
      Lighting Off
      ZWrite Off
      Blend One OneMinusSrcAlpha

      Pass
      {
          Stencil
          {
              Ref 4
              Comp Always
              Pass Replace
          }

      CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #pragma multi_compile _ PIXELSNAP_ON
          #include "UnityCG.cginc"

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

          struct v2f
          {
              float4 vertex   : SV_POSITION;
              fixed4 color    : COLOR;
              half2 texcoord  : TEXCOORD0;
          };

          fixed4 _Color;
          fixed _AlphaCutoff;

          v2f vert( appdata_t IN )
          {
              v2f OUT;
              OUT.vertex = UnityObjectToClipPos( IN.vertex );
              OUT.texcoord = IN.texcoord;
              OUT.color = IN.color * _Color;
              #ifdef PIXELSNAP_ON
              OUT.vertex = UnityPixelSnap( OUT.vertex );
              #endif

              return OUT;
          }

          sampler2D _MainTex;
          sampler2D _AlphaTex;


          fixed4 frag( v2f IN ) : SV_Target
          {
              fixed4 c = tex2D( _MainTex, IN.texcoord ) * IN.color;
              c.rgb *= c.a;

              // here we discard pixels below our _AlphaCutoff so the stencil buffer only gets written to
              // where there are actual pixels returned. If the occluders are all tight meshes (such as solid rectangles)
              // this is not necessary and a non-transparent shader would be a better fit.
              //clip( c.a - _AlphaCutoff );

              return c;
          }
      ENDCG
      }
  }
}

This is the shader coder for the “occluded”:

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

Shader "Sprites/Occluded"
{
  Properties
  {
     [PerRendererData] _MainTex ( "Sprite Texture", 2D ) = "white" {}
      _Color ( "Tint", Color ) = ( 1, 1, 1, 1 )
     [MaterialToggle] PixelSnap ( "Pixel snap", Float ) = 0
     [PerRendererData] _OccludedColor ( "Occluded Tint", Color ) = ( 0, 0, 0, 0.5 )
  }


CGINCLUDE

// shared structs and vert program used in both the vert and frag programs
struct appdata_t
{
  float4 vertex   : POSITION;
  float4 color    : COLOR;
  float2 texcoord : TEXCOORD0;
};

struct v2f
{
  float4 vertex   : SV_POSITION;
  fixed4 color    : COLOR;
  half2 texcoord  : TEXCOORD0;
};


fixed4 _Color;
sampler2D _MainTex;


v2f vert( appdata_t IN )
{
  v2f OUT;
  OUT.vertex = UnityObjectToClipPos( IN.vertex );
  OUT.texcoord = IN.texcoord;
  OUT.color = IN.color * _Color;
  #ifdef PIXELSNAP_ON
  //OUT.vertex = UnityPixelSnap( OUT.vertex );
  #endif

  return OUT;
}

ENDCG



  SubShader
  {
      Tags
      {
          "Queue" = "Transparent"
          "IgnoreProjector" = "True"
          "RenderType" = "Transparent"
          "PreviewType" = "Plane"
          "CanUseSpriteAtlas" = "True"
      }

      Cull Off
      Lighting Off
      ZWrite Off
      Blend One OneMinusSrcAlpha

      Pass
      {
          Stencil
          {
              Ref 4
              Comp NotEqual
          }


      CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #pragma multi_compile _ PIXELSNAP_ON
          #include "UnityCG.cginc"


          fixed4 frag( v2f IN ) : SV_Target
          {
              fixed4 c = tex2D( _MainTex, IN.texcoord ) * IN.color;
              c.rgb *= c.a;
              return c;
          }
      ENDCG
      }


      // occluded pixel pass. Anything rendered here is behind an occluder
      Pass
      {
          Stencil
          {
              Ref 4
              Comp Equal
          }

      CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #pragma multi_compile _ PIXELSNAP_ON
          #include "UnityCG.cginc"

          fixed4 _OccludedColor;


          fixed4 frag( v2f IN ) : SV_Target
          {
              fixed4 c = tex2D( _MainTex, IN.texcoord );
              if (c.r < .5 && c.g < .5 && c.b < .5 ){
                return _OccludedColor * c;
              } else {
                return _OccludedColor * c.a * _OccludedColor.a;
              }
          }
      ENDCG
      }
  }
}

So I am at a loss right now. I really wanna use shader graph for my project but this stencil shader is very important so I need it to work but I don’t understand why it broke. Thanks for your time.

I got it to work with LWRP! So to not go DenverCoder9 on you, here is how I did it in the hopes, that it helps someone who wants to transfer their legacy multi pass shader and/or stencil shader to the Lightweight Render Pipeline.

First, you have to know, that old multi pass shaders do not work by design.

I tried to transfer from what I understood the shader does to the overrides:

Create a custom renderer for LWRP:
Custom Renderer for LWRP

Deselect the layer for which to use the “occluded” shader (in my case player):
Deselect target layer for your stencil shader

Create a “renderer feature”, select target layer to be rendered. Since I use sprites I need to select Queue: Transparent and Event: After Rendering Transparents this depends on the target game object. Override the “stencil”, value 4, Compare function “not equal” and “Keep” in all three cases. This one makes sure you render the layer again since we de-selected it from the default layer mask.
StencilNotReplaced Renderer Feature

Create a second “renderer feature”, select everything the same except the compare function, here “equal”. For this renderer feature we also override the material with our “occluded” material that uses the shader.
StencilReplaced Renderer Feature

You need to apply the “occluder” shader to an object so when both shaders are used at the same time, the stencil comparison will be triggered.

Lastly change the “occluded” shader from the question by deleting the first pass, only keep the second one after line 95 (starting with “occluded pixel pass. Anything rendered here is behind an occluder”).

Here are some helpful links I found along the way to illuminate the stencil shader a bit and might include more reference points:

Leeloo Dallas Multi-pass