Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Need some help with transparent doouble sided shader

Discussion in 'Shaders' started by Rusoski, Jun 16, 2021.

  1. Rusoski

    Rusoski

    Joined:
    Nov 6, 2013
    Posts:
    63
    Good day community, I am trying to learn shaders and I am stuck for a few days now with this one, my goal here is to make a transparent shader that could render both sides of an objects, I want to write this shader for grass, I show you my code below:

    Code (CSharp):
    1. // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
    2.  
    3. Shader "Rusoski/Grass"
    4. {
    5.     Properties
    6.     {
    7.          _Color ("Main Color", Color) = (1,1,1,1)
    8.          _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    9.     }
    10.  
    11.     SubShader
    12.     {
    13.         Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    14.         ZWrite On
    15.         Blend SrcAlpha OneMinusSrcAlpha
    16.         Cull off
    17.         LOD 200
    18.  
    19.         Pass
    20.         {
    21.             Blend SrcAlpha OneMinusSrcAlpha
    22.  
    23.             CGPROGRAM
    24.  
    25.             #pragma vertex vert alpha
    26.             #pragma fragment frag alpha
    27.  
    28.             #include "UnityCG.cginc"
    29.  
    30.             struct appdata
    31.             {
    32.                 float4 vertex : POSITION;
    33.                 float2 uv : TEXCOORD0;
    34.             };
    35.  
    36.             struct v2f
    37.             {
    38.                 float4 vertex : SV_POSITION;
    39.                 float2 uv : TEXCOORD0;
    40.             };
    41.  
    42.             sampler2D _MainTex;
    43.             fixed4 _MainTex_ST;
    44.             fixed4 _Color;
    45.  
    46.             v2f vert(appdata v)
    47.             {
    48.                 v2f o;
    49.                 o.vertex = UnityObjectToClipPos(v.vertex);
    50.                 o.uv = v.uv;
    51.                 return o;
    52.             }
    53.  
    54.             fixed4 frag(v2f i) : COLOR
    55.             {
    56.                 fixed4 col = tex2D(_MainTex, i.uv);
    57.                 return col * _Color;
    58.             }
    59.  
    60.             ENDCG
    61.         }
    62.     }
    63.     Fallback "Transparent/Cutout/VertexLit"
    64. }
    The result is this:



    If I change the ZWrite to Off, it will look like this:



    There is a clear issue with the sorting order.

    Note that I want to use vert and frag functions, I do not want to mess with surface shaders yet.

    Any help is very much appreciated
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,402
    You can't use alpha blending and depth writes on a single object and expect good results.

    If you need accurate sorting, you must use alpha testing (aka alpha cutout) and not alpha blending.

    If you need alpha blending, you must be okay with possibly having bad sorting.


    ZWrite On
    is used to sort opaque pixels via the depth buffer. When a mesh renders with
    ZWrite On
    (which is also the default if the line is removed) and
    ZTest LEqual
    (also the default) the GPU calculates the depth of each pixel before rendering them. If the depth is further away than the value in the depth buffer, that pixel of the surface is skipped. If it's closer then it replaces the value in the depth buffer and renders the color to that pixel. This works great for opaque surfaces because you either skip or completely replace the existing color value. For transparent surfaces they need to be sorted back to front prior to rendering to appear correct. The depth buffer doesn't help with this because it can only hold one depth value, and the frame buffer can only hold one color value per pixel. So if transparent objects are rendered out of order there's no way to render a surface "behind" something that's already previously rendered. Additionally,
    ZWrite On
    will still render to the depth buffer in areas that are completely transparent due to the blend ... the surface is still being rendered at those pixels, and the depth test and writes happen before the blend even happens.

    Alpha testing on the other hand tells the GPU to skip rendering pixels in the fragment shader, using
    clip()
    or
    discard
    . In that case the GPU waits until after the fragment shader to write to the depth buffer. And while technically you can use alpha testing and alpha blending in the same shader, but the above issues are going to persist.


    I've written more at length on it here:
    https://realtimevfx.com/t/transparency-issues-in-unity/4337
    And a "solution" here:
    https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f
    And another solution here:
    https://forum.unity.com/threads/sta...y-shiny-on-the-underside.393068/#post-2574717

    That last option uses a two pass shader where the first pass renders to the depth buffer with an alpha tested pass that's "smaller" than the alpha blended pass. The alpha blended pass is then rendered after that with the alpha rescaled so it only blends "outside" the alpha tested area. The result in the visibly opaque parts of the mesh sort correctly, and only the narrow region that is alpha blended will have sorting issues. However it should be noted this technique will appear exactly the same as normal alpha testing when the skybox is visible behind the object in the Game view, and shadow receiving is a little weird.