Fade Opaque Object using Dithering

Mar 11, 2022

Dithering is an option to make an object transparent or fade in/out without using transparency.

Here's a function to dither without using a texture.

float dithering(float2 pos) {
    pos *= _ScreenParams.xy;

    float DITHER_THRESHOLDS[16] =
    {
        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
    };

    int index = (int(pos.x) % 4) * 4 + int(pos.y) % 4;
    return DITHER_THRESHOLDS[index];
}

Here's a complete unity unlit shader that fades out over a distance. (untested)

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;

                // need these
                float4 screenPosition : TEXCOORD1;
                float ditherAmount : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float maxDistance;
            float fadeDistance;

            float dithering(float2 pos) {
                pos *= _ScreenParams.xy;

                float DITHER_THRESHOLDS[16] =
                {
                    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
                };

                int index = (int(pos.x) % 4) * 4 + int(pos.y) % 4;
                return DITHER_THRESHOLDS[index];
            }

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                // pass to fragment shader
                o.screenPosition = ComputeScreenPos(o.vertex);

                float dstToCamera = distance(_WorldSpaceCameraPos, v.vertex);
                float fadeStart = maxDistance - fadeDistance;
                o.ditherAmount = clamp(((dstToCamera - fadeStart) / maxDistance) / (fadeStart / maxDistance), 0, 1);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);

                // fade by dithering
                float2 screenUV = IN.screenPosition.xy / IN.screenPosition.w;
                clip(dithering(screenUV - i.ditherAmount);

                return col;
            }
            ENDCG
        }
    }
}