Search Unity

Resolved Liquid shader front/back face rendering order issue

Discussion in 'Shaders' started by Renatusdev, Mar 29, 2021.

  1. Renatusdev

    Renatusdev

    Joined:
    Dec 9, 2018
    Posts:
    35
    So I'm making a liquid shader that takes a mesh and renders only the values below a certain threshold, like this:
    https://www.patreon.com/posts/18245226

    My issue is that the backfaces are overlapping the frontfaces at the given fill rate.

    I'd love to understand why.

    Here's my code and an image of what it looks like

    Screen Shot 2021-03-28 at 9.30.53 PM.png

    Code (CSharp):
    1. Shader "Renatus/Liquid"
    2. {
    3.     Properties
    4.     {
    5.         [HDR]_LiquidColor("Liquid Color", Color) = (1,1,1,1)
    6.         _FillAmount ("Fill Amount", Range(0, 1)) = 0.4
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Transparent" "Queue"="Transparent"}
    11.         Blend SrcAlpha OneMinusSrcAlpha
    12.         Cull Off
    13.         ZWrite Off
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             float4 _LiquidColor;
    24.             float _FillAmount;
    25.  
    26.             struct MeshData
    27.             {
    28.                 float4 vertex : POSITION;
    29.                 float2 uv : TEXCOORD0;
    30.             };
    31.  
    32.             struct Interpolator
    33.             {
    34.                 float4 vertex : SV_POSITION;
    35.                 float2 uv : TEXCOORD0;
    36.                 float3 world : TEXCOORD1;
    37.             };
    38.  
    39.             Interpolator vert (MeshData v)
    40.             {
    41.                 Interpolator o;
    42.                
    43.                 o.vertex = UnityObjectToClipPos(v.vertex);
    44.                 o.world = mul(unity_ObjectToWorld, v.vertex).xyz;
    45.                 o.uv = v.uv;
    46.                 return o;
    47.             }
    48.  
    49.             float4 frag (Interpolator i, fixed facing : VFACE) : SV_Target
    50.             {
    51.                 float fragWorldY = i.world.y;
    52.                 float centerWorldY = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).y;
    53.                 float distanceFromFragToCenter = fragWorldY - centerWorldY;;
    54.                 float fillAmount = step(distanceFromFragToCenter, _FillAmount * 2 - 1);
    55.                 float4 liquid = _LiquidColor * fillAmount;
    56.                 float4 foam = fillAmount;
    57.  
    58.                 return facing > 0 ? liquid : foam;
    59.             }
    60.  
    61.             ENDCG
    62.         }
    63.     }
    64. }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    MinionArt's example shader is alpha tested. Or more specifically it's using
    AlphaToMask On
    , which is enabling alpha to coverage and desktop GPUs fall back to regular alpha testing when MSAA is not enabled.

    If you're looking to you alpha blending you must use a two pass shader which renders the back faces first and then the front faces, or approximate the back face using an analytical or precomputed SDF of some kind.

    Why? Because correct and efficient sorting of transparency is an unsolved problem for real time rendering.
    https://realtimevfx.com/t/transparency-issues-in-unity/4337/2
     
    Renatusdev likes this.
  3. Renatusdev

    Renatusdev

    Joined:
    Dec 9, 2018
    Posts:
    35
    Thanks for the answer! I've spent some time trying to understand it and have now decided to lean for the two pass solution. My issue now though is doing the right semantics for two passes.

    I followed this mega simple tutorial here from 2020: https://www.codinblack.com/shader-pass-and-multi-pass-shader/
    and it does not work for me. Like, I literally copy pasted the code, and the outline did not appear. Is it cause I'm in URP w forward renderer? Is there some new semantic for two pass shaders that I'm not aware of?

    Here's my code for me trying to do two passes on my shader. One with the liquid pixel stuff, and the second being all white.

    Code (CSharp):
    1. Shader "Renatus/Liquid"
    2. {
    3.     Properties
    4.     {
    5.         [HDR]_LiquidColor("Liquid Color", Color) = (1,1,1,1)
    6.         [HDR] _FoamColor ("Foam Color", Color) = (1,1,1,1)
    7.         _FillAmount ("Fill Amount", Range(0, 1)) = 0.4
    8.         _FoamWidth ("Foam Width", Range(0, 1)) = 0.5
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    13.         Blend SrcAlpha OneMinusSrcAlpha
    14.         ZWrite Off
    15.  
    16.         Pass
    17.         {
    18.             Cull Back
    19.             CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.  
    23.             #include "UnityCG.cginc"
    24.  
    25.             float4 _LiquidColor;
    26.             float4 _FoamColor;
    27.             float _FillAmount;
    28.             float _FoamWidth;
    29.  
    30.             struct MeshData
    31.             {
    32.                 float4 vertex : POSITION;
    33.                 float2 uv : TEXCOORD0;
    34.             };
    35.  
    36.             struct Interpolator
    37.             {
    38.                 float4 vertex : SV_POSITION;
    39.                 float2 uv : TEXCOORD0;
    40.                 float3 world : TEXCOORD1;
    41.             };
    42.  
    43.             Interpolator vert (MeshData v)
    44.             {
    45.                 Interpolator o;
    46.            
    47.                 o.vertex = UnityObjectToClipPos(v.vertex);
    48.                 o.world = mul(unity_ObjectToWorld, v.vertex).xyz;
    49.                 o.uv = v.uv;
    50.                 return o;
    51.             }
    52.  
    53.             float4 frag (Interpolator i, fixed facing : VFACE) : SV_Target
    54.             {
    55.                 float fragWorldY = i.world.y;
    56.                 float centerWorldY = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).y;
    57.                 float distanceFromFragToCenter = fragWorldY - centerWorldY;
    58.                 float fill = ( _FillAmount * 2 - 1);
    59.                 float liquidMask = step(distanceFromFragToCenter, fill);
    60.                 float foamMask = step(distanceFromFragToCenter, fill+(_FoamWidth * 0.2)) - liquidMask;
    61.  
    62.                 float4 liquid = _LiquidColor * liquidMask;
    63.                 float4 foam = _FoamColor * foamMask;
    64.            
    65.                 return liquid + foam;
    66.             }
    67.  
    68.             ENDCG
    69.         }
    70.  
    71.         Pass
    72.         {
    73.             CGPROGRAM
    74.             #pragma vertex vert
    75.             #pragma fragment frag
    76.  
    77.             #include "UnityCG.cginc"
    78.  
    79.             struct MeshData
    80.             {
    81.                 float4 vertex : POSITION;
    82.                 float2 uv : TEXCOORD0;
    83.             };
    84.  
    85.             struct Interpolator
    86.             {
    87.                 float4 vertex : SV_POSITION;
    88.                 float2 uv : TEXCOORD0;
    89.             };
    90.  
    91.             Interpolator vert (MeshData v)
    92.             {
    93.                 Interpolator o;
    94.            
    95.                 o.vertex = UnityObjectToClipPos(v.vertex);
    96.                 o.uv = v.uv;
    97.                 return o;
    98.             }
    99.  
    100.             float4 frag (Interpolator i, fixed facing : VFACE) : SV_Target
    101.             {
    102.                 return 1;
    103.             }
    104.  
    105.             ENDCG
    106.         }
    107.     }
    108. }
    The image below shows that nothing changed.
    Screen Shot 2021-03-29 at 3.34.45 PM.png
     
    Last edited: Mar 29, 2021
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Yes. URP does not support multi pass shaders. Or more specifically does not support two lit passes in a single shader. You can only do a single unlit pass and a single lit pass per shader. For URP you have to do it with a Render Feature or multiple materials.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    For clarification, you can have one pass with
    Tags{ "LightMode" = "UniversalForward" }
    and one pass with no
    "LightMode"
    tag or
    Tags{ "LightMode" = "SRPDefaultUnlit" }
    .
     
  6. Renatusdev

    Renatusdev

    Joined:
    Dec 9, 2018
    Posts:
    35
    Thanks bgolus! I made it work with by using the LightMode passes. Although since the intention of these passes has to do with light I feel like there's probably something wrong with my approach. Perhaps you might know of this?

    Also, there are some calculations that I have to do for both passes in the fragment function, yet the calculations are to attain the same output value. Is there a way to pass this output value from one pass to another as a uniform variable (I think that's the term)?

    Code (CSharp):
    1.              
    2. float4 frag (Interpolator i) : SV_Target
    3. {
    4. // [...]
    5. // evil calculations creating values A, B, and C
    6. // [...]
    7. float outputIwantInBothFragPasses = A + B + C;
    8. }
    9.  
    Instead of doing the calculations in both fragment functions
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    No.

    I mean, technically yes, but not in a way that'll be faster than calculating it in both pass separately.

    Generally speaking uniforms are assigned by the CPU before rendering begins and cannot be changed by the GPU afterwards. There are some tricks using structured buffers or render textures that can be abused to pass data between passes, in which case the uniforms aren't being changed per-say, but rather the data in the object assigned to a uniform. But this is can be complicated when being used in a real world setup and, as I said, isn't likely to be faster than just calculating the data again. GPUs can do a lot of math very fast, usually way faster than passing data around.
     
    Renatusdev likes this.
  8. Renatusdev

    Renatusdev

    Joined:
    Dec 9, 2018
    Posts:
    35
    Ok thanks for all the help!
     
  9. Renatusdev

    Renatusdev

    Joined:
    Dec 9, 2018
    Posts:
    35
    To anyone wondering, here's the liquid shader code.

    Code (CSharp):
    1. Shader "Renatus/Liquid"
    2. {
    3.     Properties
    4.     {
    5.         [HDR]_LiquidColor("Liquid Color", Color) = (1,1,1,1)
    6.         [HDR] _FoamColor ("Foam Color", Color) = (1,1,1,1)
    7.         [HDR] _EdgeRingColor ("Edge Ring Color", Color) = (1,1,1,1)
    8.         [HDR] _FresnelColor ("Fresnel Color", Color) = (1,1,1,1)
    9.         _FillAmount ("Fill Amount", Range(0, 1)) = 0.4
    10.         _FoamWidth ("Foam Width", Range(0, 1)) = 0.5
    11.         _FresnelPower ("Fresnel", Range(0, 1)) = 0.5
    12.     }
    13.     SubShader
    14.     {
    15.         Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    16.         Blend SrcAlpha OneMinusSrcAlpha
    17.         ZWrite Off
    18.  
    19.         Pass
    20.         {
    21.             Tags {"LightMode"="UniversalForward"}
    22.  
    23.             Cull Back
    24.             CGPROGRAM
    25.             #pragma vertex vert
    26.             #pragma fragment frag
    27.  
    28.             #include "UnityCG.cginc"
    29.  
    30.             float4 _LiquidColor;
    31.             float4 _EdgeRingColor;
    32.             float4 _FresnelColor;
    33.             float _FoamWidth;
    34.             float _FillAmount;
    35.             float _FresnelPower;
    36.  
    37.             struct MeshData
    38.             {
    39.                 float4 vertex : POSITION;
    40.                 float3 normal : NORMAL;
    41.                 float2 uv : TEXCOORD0;
    42.             };
    43.  
    44.             struct Interpolator
    45.             {
    46.                 float4 vertex : SV_POSITION;
    47.                 float2 uv : TEXCOORD0;
    48.                 float3 world : TEXCOORD1;
    49.                 float3 normal : TEXCOORD2;
    50.             };
    51.  
    52.             Interpolator vert (MeshData v)
    53.             {
    54.                 Interpolator o;
    55.                
    56.                 o.vertex = UnityObjectToClipPos(v.vertex);
    57.                 o.world = mul(unity_ObjectToWorld, v.vertex).xyz;
    58.                 o.normal = UnityObjectToWorldNormal(v.normal);
    59.                 o.uv = v.uv;
    60.                 return o;
    61.             }
    62.  
    63.             float4 frag (Interpolator i, fixed facing : VFACE) : SV_Target
    64.             {              
    65.                 // World position of current fragment
    66.                 float fragWorldY = i.world.y;
    67.  
    68.                 // World position of the objects' center
    69.                 float centerWorldY = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).y;
    70.                 float distanceFromFragToCenter = fragWorldY - centerWorldY;
    71.  
    72.                 // Amount to fill              
    73.                 float t = sin(_Time.y * 0.75) * 0.05;
    74.                 float fill = ((_FillAmount + t) * 2 - 1);
    75.                
    76.                 // Mask creations
    77.                 float liquidMask = step(distanceFromFragToCenter, fill);
    78.                 float foamMask = step(distanceFromFragToCenter, fill+(_FoamWidth * 0.2)) - liquidMask;
    79.  
    80.                 // Adding Color
    81.                 float4 liquid = _LiquidColor * liquidMask;
    82.                 float4 foam = _EdgeRingColor * foamMask;
    83.                
    84.                 // Adding Fresnel
    85.                 float fresnelAnimation = 1 - (_FresnelPower * (sin(_Time.y) * 0.3) + 0.4);
    86.                 float fresnelMask = pow(saturate(1-dot(normalize(_WorldSpaceCameraPos - i.world), normalize(i.normal))), (1-fresnelAnimation) * 8);
    87.                 float4 fresnel = fresnelMask * liquidMask * _FresnelColor;
    88.  
    89.                 return liquid + foam + fresnel;
    90.             }
    91.  
    92.             ENDCG
    93.         }
    94.  
    95.         Pass
    96.         {
    97.             Cull Front
    98.             CGPROGRAM
    99.             #pragma vertex vert
    100.             #pragma fragment frag
    101.  
    102.             #include "UnityCG.cginc"
    103.  
    104.             float4 _LiquidColor;
    105.             float4 _FoamColor;
    106.             float _FoamWidth;
    107.             float _FillAmount;
    108.  
    109.             struct MeshData
    110.             {
    111.                 float4 vertex : POSITION;
    112.                 float2 uv : TEXCOORD0;
    113.             };
    114.  
    115.             struct Interpolator
    116.             {
    117.                 float4 vertex : SV_POSITION;
    118.                 float2 uv : TEXCOORD0;
    119.                 float3 world : TEXCOORD1;
    120.             };
    121.  
    122.             Interpolator vert (MeshData v)
    123.             {
    124.                 Interpolator o;
    125.                
    126.                 o.vertex = UnityObjectToClipPos(v.vertex);
    127.                 o.world = mul(unity_ObjectToWorld, v.vertex).xyz;
    128.                 o.uv = v.uv;
    129.                 return o;
    130.             }
    131.  
    132.             float4 frag (Interpolator i) : SV_Target
    133.             {
    134.                 // World position of current fragment
    135.                 float fragWorldY = i.world.y;
    136.  
    137.                 // World position of the objects' center
    138.                 float centerWorldY = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).y;
    139.                 float distanceFromFragToCenter = fragWorldY - centerWorldY;
    140.  
    141.                 // Amount to fill              
    142.                 float t = sin(_Time.y * 0.75) * 0.05;
    143.                 float fill = ((_FillAmount + t) * 2 - 1);
    144.                
    145.                 // Mask creations
    146.                 float liquidMask = step(distanceFromFragToCenter, fill);
    147.                 float foamMask = step(distanceFromFragToCenter, fill+(_FoamWidth * 0.2)) - liquidMask;
    148.  
    149.                 return (liquidMask + foamMask)* _FoamColor;
    150.             }
    151.  
    152.             ENDCG
    153.         }
    154.     }
    155. }
     
  10. AmberElferink

    AmberElferink

    Joined:
    Jan 28, 2019
    Posts:
    4
    Thanks so much for posting! That fixed all my problems. On Built-In renderer I simply deleted Tags {"LightMode"="UniversalForward"}, and it worked :)