Dithering transparency shader with shadow

This is a shader created by ocias but the shadow does not dither with the object. Any idea how to make it works? Thanks!

// Standard shader with stipple transparency
// by Alex Ocias - https://ocias.com

// based on an article by Digital Rune: https://www.digitalrune.com/Blog/Post/1743/Screen-Door-Transparency

Shader "Ocias/Standard (Stipple Transparency)" {
    Properties{
        _Color("Color", Color) = (1,1,1,1)
        _ShadowColor("ShadowColor", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("Bumpmap", 2D) = "bump" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.5
        _Transparency("Transparency", Range(0,1)) = 1.0
    }
        SubShader{
            Tags { "RenderType" = "Opaque" }
            LOD 100

            CGPROGRAM
            // Physically based Standard lighting model, and enable shadows on all light types
            #pragma surface surf Standard fullforwardshadows addshadow

            // Use shader model 3.0 target, to get nicer looking lighting
            #pragma target 3.0

            sampler2D _MainTex;
            sampler2D _BumpMap;

            struct Input {
                float2 uv_MainTex;
                float2 uv_BumpMap;
                float4 screenPos;
            };

            half _Glossiness;
            half _Metallic;
            half _Transparency;
            fixed4 _Color;
            fixed4 _ShadowColor;

            void surf(Input IN, inout SurfaceOutputStandard o) {
                // Albedo comes from a texture tinted by color
                fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
                o.Albedo = c.rgb;
                // Metallic and smoothness come from slider variables
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
                o.Alpha = c.a;

                // Screen-door transparency: Discard pixel if below threshold.
                float4x4 thresholdMatrix =
                {  1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
                  13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
                   4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
                  16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
                };
                float4x4 _RowAccess = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
                float2 pos = IN.screenPos.xy / IN.screenPos.w;
                pos *= _ScreenParams.xy; // pixel position
                clip(_Transparency - thresholdMatrix[fmod(pos.x, 4)] * _RowAccess[fmod(pos.y, 4)]);
            }
            ENDCG
        }

        FallBack "Diffuse"
}

There’s a long standing bug with Surface Shaders that the screenPos is not set for the shadow caster pass, and it is instead set to all zeros. The only work around for this is to not use the built in screenPos and instead calculate it yourself with a custom vertex function.

#pragma surface surf Standard fullforwardshadows addshadow vertex:vert

struct Input {
  float2 uv_MainTex;
  // float2 uv_BumpMap; // you probably shouldn't use this unless you actually require a separate scale and offset for your bump texture, just reuse uv_MainTex for the normal map
  float4 myScreenPos; // can't be called screenPos
};

void vert (inout appdata_full v, out Input o)
{
  o.myScreenPos = ComputeScreenPos(UnityObjectToClipPos(v.vertex));
}

// in the surf function
float2 pos = IN.myScreenPos.xy / IN.myScreenPos.w;
1 Like

funnily enough in unity 2021.3.5f1 i get the shadow dithered properly.

(once i edited line 62 in OPs shader to not only use _Transparency but also the textures alpha channel)

maybe i didnt understand the problem at hand? can you explain it to me bgolus? dont want to waste your time, just curious to learn what i am missing here.

What about not surface shaders? I working on some and had the same problem, actually I never had this in ASE shader Editor, I guess because it’s using ase_screenPos name. But when I’m trying to make custom shader, the problem appeared, and cannot figure out how to avoid, even if I use custom name.

I know this was asked a long time ago, but the original shader here does not have dithered shadows, or a dithered camera depth texture due to the screenPos value being broken, as stated above.

This means the above shader, with the modification to like 62, looks like this:
8806954--1197853--upload_2023-2-14_16-1-14.png
Notice the shadow is a solid circle, and that you can’t even see the shadow “thru” the object.

With the suggested fix you get a properly dithered shadow, and you can see the shadow through the object.
8806954--1197850--upload_2023-2-14_16-1-5.png

For non-surface shaders, the “solution” is kind of the same… you need to pass the screen position from the vertex shader to the fragment shader and the code to do so is basically exactly the same as the example I posted in terms of the main steps.

What you might be missing is you also need to actually write a custom shadow caster pass. You can’t just throw an addshadow in there someplace as if you’re writing a vertex fragment shader you’re explicitly not making use of Unity’s shader generation code, which is what Surface Shaders are.

1 Like