Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Custom Dither Transparency Shader

Discussion in 'Shaders' started by alchemist_wurzelpurzel, Sep 29, 2021.

  1. alchemist_wurzelpurzel

    alchemist_wurzelpurzel

    Joined:
    Sep 4, 2018
    Posts:
    43
    Hi all!

    For our project we wanted our opaque geometry to be able to fade out without them being rendered as transparent. So we decided to use the dithering transparency (also called screen-door transparency) technique to use the alpha clipping to fade out parts of the geometry depending on its transparency value.

    Since we still wanted to be able to use the URP Lit Shader functionality, I copied all the Lit shader files and includes and modified the SurfaceInput Alpha() function to accomodate for dithering which was easy enough. I just needed the Clip Space position as an additional parameter and changed the function calls accordingly and it worked! Except it didn't quite work as it should.

    On the screenshot you can see two spheres:
    The left one uses our custom DitheredLit shader and the right one uses a simple vertex/fragment shader that does the same thing for testing purposes.

    Now after lots of digging around I am fairly confident that the Clip Space position is the issue here and something called Homogenous Clip Space because I believe in the Lit shader the CS position is homogenous whereas in the simple vertex/fragment shader it isn't. But that's all I was able to find out and now I am stuck.

    Here is my custom Alpha function in the custom SurfaceInput.hlsl file for the Lit shader:
    Code (CSharp):
    1. half DitheredAlpha(half albedoAlpha, half4 color, half cutoff, half transparency, half ditherSize, float4 positionCS, float2 uv)
    2. {
    3. #if !defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A) && !defined(_GLOSSINESS_FROM_BASE_ALPHA)
    4.     half alpha = albedoAlpha * transparency;
    5. #else
    6.     half alpha = transparency;
    7. #endif
    8.  
    9. #if defined(_ALPHATEST_ON)
    10.     float DITHER_THRESHOLDS[16] =
    11.     {
    12.         1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
    13.         13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
    14.         4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
    15.         16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
    16.     };
    17.  
    18.     //float4 screenPos = ComputeScreenPos(positionCS);
    19.     //uv = screenPos.xy / screenPos.w;
    20.     uv = positionCS.xy / positionCS.w;
    21.     uv *= _ScreenParams.xy;
    22.     uint index = (uint(uv.x) % 4) * 4 + uint(uv.y) % 4;
    23.     // Returns > 0 if not clipped, < 0 if clipped based
    24.     // on the dither
    25.     clip(alpha - DITHER_THRESHOLDS[index]);
    26. #endif
    27.  
    28.     return alpha;
    29. }
    And here is the simple vertex/fragment shader for testing purposes:
    Code (CSharp):
    1. Shader "Custom/SurfaceDither"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _Transparency ("Transparency", Range(0,1)) = 1.0
    7.     }
    8.     SubShader
    9.     {
    10.         Tags
    11.         {
    12.             "RenderType"="Opaque"
    13.         }
    14.         Pass
    15.         {
    16.             CGPROGRAM
    17.             #include "UnityCG.cginc"
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             float4 _Color;
    22.             float _Transparency;
    23.        
    24.             struct v2f
    25.             {
    26.                 float4 pos      : POSITION;
    27.                 float4 col      : COLOR;
    28.                 float4 spos     : TEXCOORD1;
    29.             };
    30.  
    31.             v2f vert(appdata_base v)
    32.             {
    33.                 v2f o;
    34.                 o.pos = UnityObjectToClipPos(v.vertex);
    35.                 o.spos = ComputeScreenPos(o.pos);
    36.  
    37.                 return o;
    38.             }
    39.  
    40.             float4 frag(v2f i) : COLOR
    41.             {
    42.                 float4 col = _Color;
    43.            
    44.                 float2 pos = i.spos.xy / i.spos.w;
    45.                 pos *= _ScreenParams.xy;
    46.  
    47.                 // Define a dither threshold matrix which can
    48.                 // be used to define how a 4x4 set of pixels
    49.                 // will be dithered
    50.                 float DITHER_THRESHOLDS[16] =
    51.                 {
    52.                     1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
    53.                     13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
    54.                     4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
    55.                     16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
    56.                 };
    57.  
    58.                 int index = (int(pos.x) % 4) * 4 + int(pos.y) % 4;
    59.                 clip(_Transparency - DITHER_THRESHOLDS[index]);
    60.            
    61.                 return col;
    62.             }
    63.             ENDCG
    64.         }
    65.     }
    66.     Fallback "VertexLit"
    67. }
    We are using Unity 2020.3.17f1 and URP 10.6.0.

    I would appreciate any help, thanks!
     

    Attached Files:

    Last edited: Sep 29, 2021
  2. alchemist_wurzelpurzel

    alchemist_wurzelpurzel

    Joined:
    Sep 4, 2018
    Posts:
    43
    Found the solution!
    I was looking for a way to properly reconstruct the screen position and this helped me:
    https://docs.unity3d.com/Packages/c...g-shaders-urp-reconstruct-world-position.html

    So the final solution is pretty simple:
    Code (CSharp):
    1. half DitheredAlpha(half albedoAlpha, half4 color, half cutoff, half transparency, half ditherSize, float4 positionCS)
    2. {
    3. #if !defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A) && !defined(_GLOSSINESS_FROM_BASE_ALPHA)
    4.     half alpha = albedoAlpha * transparency;
    5. #else
    6.     half alpha = transparency;
    7. #endif
    8.  
    9. #if defined(_ALPHATEST_ON)
    10.     float DITHER_THRESHOLDS[16] =
    11.     {
    12.         1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
    13.         13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
    14.         4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
    15.         16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
    16.     };
    17.    
    18.     float2 uv = positionCS.xy / _ScaledScreenParams.xy;
    19.     uv *= _ScreenParams.xy;  
    20.     uint index = (uint(uv.x) % 4) * 4 + uint(uv.y) % 4;
    21.     // Returns > 0 if not clipped, < 0 if clipped based
    22.     // on the dither
    23.     clip(alpha - DITHER_THRESHOLDS[index]);
    24. #endif
    25.  
    26.     return alpha;
    27. }
     
    sirleto likes this.
  3. alchemist_wurzelpurzel

    alchemist_wurzelpurzel

    Joined:
    Sep 4, 2018
    Posts:
    43
    Reviving my own post here because we found the shader to not properly work with transparent rendering. For anyone looking for a solution to this, here is the correct DitheredAlpha function that also works with transparency:

    Code (CSharp):
    1. half DitheredAlpha(half albedoAlpha, half4 color, half cutoff, half transparency, half ditherSize, float4 positionCS)
    2. {
    3. #if !defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A) && !defined(_GLOSSINESS_FROM_BASE_ALPHA)
    4.     half alpha = albedoAlpha * color.a;
    5. #else
    6.     half alpha = color.a;
    7. #endif
    8. #if defined(_ALPHATEST_ON)
    9.     float DITHER_THRESHOLDS[16] =
    10.     {
    11.         1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
    12.         13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
    13.         4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
    14.         16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
    15.     };
    16.  
    17.     float2 uv = positionCS.xy / _ScaledScreenParams.xy;
    18.     uv *= _ScreenParams.xy;
    19.     uint index = (uint(uv.x) % 4) * 4 + uint(uv.y) % 4;
    20.     // Returns > 0 if not clipped, < 0 if clipped based
    21.     // on the dither
    22.     clip(transparency - DITHER_THRESHOLDS[index]);
    23. #endif
    24.     return alpha;
    25. }
     
    KarlKarl2000 likes this.