Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Let us know a bit about your interests, and if you'd like to become more directly involved. Take our survey!
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Render back faces depending on front faces

Discussion in 'Shaders' started by PsychoHead, May 16, 2018.

  1. PsychoHead

    PsychoHead

    Joined:
    Sep 14, 2012
    Posts:
    9
    I'm trying to create an arbitrarily shaped liquid volume (bottle). For that I am masking my volume above a certain height and rendering back faces, as shown in the picture:


    Now as a next step I want to add basic transparency. Naively setting up transparency ends up in both faces being drawn over each other:


    Right now I am trying to figure out how to mask the back face in a way that only the 'top' part is being drawn, but the back face is not showing through the red part of the front.

    I am pretty sure it's a matter of the right blend settings, but I can't figure them out. Any hints?
    Thanks!
     
    Seyed_Morteza_Kamaly likes this.
  2. GameDevCouple_I

    GameDevCouple_I

    Joined:
    Oct 5, 2013
    Posts:
    894
  3. PsychoHead

    PsychoHead

    Joined:
    Sep 14, 2012
    Posts:
    9
    Yes, this exactly the way I am doing it. It does not sort transparency out, though.
    I suppose one way to do it is drawing it completely opaque like that and using a grabpass, but that seems kind of overkill?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    4,993
    It's not something solved with blend settings. Once the green "top surface" gets rendered there's nothing you can do to remove it from areas you don't want it to be afterwards.

    The solution is to prevent the top surface from being rendered at all when it's behind the "sides". This can be solved in a number of ways, but the most straight forward way is to flip the draw order of the two passes so the sides render first and enable ZWrite On. The slightly more complicated next easiest solution is to use stencils rather than ZWrite.
     
  5. PsychoHead

    PsychoHead

    Joined:
    Sep 14, 2012
    Posts:
    9
    I tried doing that already, yet the back faces still get drawn behind the front part. They don't get discarded when occluded by alpha < 1.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    4,993
    Then the back faces are being rendered first. I'm assuming both the front and back faces are in a single shader yes? And your using clip() to do the cutoff and not alpha? Render the front faces as the first pass with ZWrite On, then the back faces as the second pass (ZWrite optional).
     
  7. PsychoHead

    PsychoHead

    Joined:
    Sep 14, 2012
    Posts:
    9
    Correct.
    Should've added the shader code right away, sorry:

    Code (CSharp):
    1. Shader "Unlit/LiquidVolume"
    2. {
    3.     Properties
    4.     {
    5.         _FillHeight("Fill Height", Float) = 0.2
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector" = "True" }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             Cull Back
    15.             Blend SrcAlpha OneMinusSrcAlpha
    16.             // not using AlphaToMask will cause back faces not to be drawn at all
    17.             AlphaToMask On
    18.             ZWrite On
    19.  
    20.             CGPROGRAM
    21.             #pragma vertex vert
    22.             #pragma fragment frag
    23.             #pragma multi_compile_fog
    24.            
    25.             #include "UnityCG.cginc"
    26.  
    27.             struct appdata
    28.             {
    29.                 float4 vertex : POSITION;
    30.             };
    31.  
    32.             struct v2f
    33.             {
    34.                 float fillHeight : TEXCOORD1;
    35.                 UNITY_FOG_COORDS(1)
    36.                 float4 vertex : SV_POSITION;
    37.             };
    38.  
    39.             float _FillHeight;
    40.            
    41.             v2f vert (appdata v)
    42.             {
    43.                 v2f o;
    44.                 o.vertex = UnityObjectToClipPos(v.vertex);
    45.                 UNITY_TRANSFER_FOG(o,o.vertex);
    46.  
    47.                 float3 worldPos = mul(unity_ObjectToWorld, v.vertex.xyz);
    48.                 // height limit of the liquid
    49.                 o.fillHeight = worldPos.y + _FillHeight;
    50.  
    51.                 return o;
    52.             }
    53.            
    54.             fixed4 frag (v2f i) : SV_Target
    55.             {
    56.                 float4 col = step(i.fillHeight, 0) * fixed4(1, 0, 0, .5);
    57.                 UNITY_APPLY_FOG(i.fogCoord, col);
    58.                 clip(col);
    59.                 return col;
    60.             }
    61.             ENDCG
    62.         }
    63.  
    64.         Pass
    65.         {
    66.             Cull Front
    67.             Blend SrcAlpha OneMinusSrcAlpha
    68.             ZWrite On
    69.  
    70.             CGPROGRAM
    71.             #pragma vertex vert
    72.             #pragma fragment frag
    73.             #pragma multi_compile_fog
    74.  
    75.             #include "UnityCG.cginc"
    76.  
    77.             struct appdata
    78.             {
    79.                 float4 vertex : POSITION;
    80.             };
    81.  
    82.             struct v2f
    83.             {
    84.                 float fillHeight : TEXCOORD1;
    85.                 UNITY_FOG_COORDS(1)
    86.                 float4 vertex : SV_POSITION;
    87.             };
    88.  
    89.             float _FillHeight;
    90.  
    91.             v2f vert(appdata v)
    92.             {
    93.                 v2f o;
    94.                 o.vertex = UnityObjectToClipPos(v.vertex);
    95.                 UNITY_TRANSFER_FOG(o,o.vertex);
    96.  
    97.                 float3 worldPos = mul(unity_ObjectToWorld, v.vertex.xyz);
    98.                 // height limit of the liquid
    99.                 o.fillHeight = worldPos.y + _FillHeight;
    100.  
    101.                 return o;
    102.             }
    103.  
    104.             fixed4 frag(v2f i) : SV_Target
    105.             {
    106.                 float4 col = step(i.fillHeight, 0) * fixed4(0, 1, 0, 1);
    107.                 UNITY_APPLY_FOG(i.fogCoord, col);
    108.                 clip(col);
    109.                 return col;
    110.             }
    111.             ENDCG
    112.         }
    113.     }
    114. }
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    4,993
    Using AlphaToMask and Blend together is dangerous at best even if you know what you're doing. Generally speaking they should be used exclusively of the other. With out MSAA enabled, AlphaToMask On acts like clip(alpha - somevalue) where "somevalue" is potentially different depending on the hardware you're using! And I'm not just saying mobile vs. desktop, or even AMD vs Nvidia, but different families of GPUs by a single manufacturer, or even the same phone within a single generation may act differently.

    That said it's usually equivalent to clip(alpha - 0.5) on most hardware these days, but then there's no reason not to use clip() in the shader as it affords more control than trying to use the output alpha for both the blend and discarding the fragment.

    Also, with MSAA enabled AlphaToMask will do other things that you don't want here. So, remove AlphaToMask On.

    But obviously that causes the issue you comment on above. The reason for that is your line here:

    clip(col);


    This is your biggest problem. That line is saying "if any value of the col is less than zero discard the fragment". You don't want this since you don't want to clip on the color value, and the alpha by your own design is only ever 0 or 0.5, it's never less than zero so that line never does anything.

    Instead do this for both passes:

    Code (csharp):
    1.             fixed4 frag (v2f i) : SV_Target
    2.             {
    3.                 float4 col = fixed4(1, 0, 0, .5); // just the color
    4.                 UNITY_APPLY_FOG(i.fogCoord, col);
    5.                 clip(-i.fillHeight); // the important line
    6.                 return col;
    7.             }
     
    PsychoHead likes this.