Search Unity

"ZTest Always" with Sorting?

Discussion in 'Shaders' started by ShaderRig, Feb 26, 2019.

  1. ShaderRig

    ShaderRig

    Joined:
    Feb 26, 2019
    Posts:
    18
    Hi! I'm looking for some tips on how I can solve the issue below.

    The project I'm working on uses its own gizmo controls. These are placed at the center of the selected object(s). This of course means you can't see it because it is sorting correctly. I've set ZTest to Always in the shader so the gizmo now renders over everything else but now there is no depth information and the arrows won't sort against itself.

    upload_2019-2-26_22-13-22.png
    (Notice the arrow heads are rendering over the cylinders).

    How can I have the Gizmo sort with its own geometry but render on top of everything else?

    Caveat 1: We were rendering the gizmo with a second camera and then overlaying the result but our lead programmer said this method was too costly.

    Caveat 2: The arrows fade as the camera looks down them to disable movement on that axis' view plane. So I think that would eliminate stencils. (Arrow fades, hole is still in the mesh).

    Thank you for reading.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The technique I use to handle this kind of effect is something I call z punching. The idea is to basically put a "hole" in the depth buffer before rendering your object.

    You need a shader with two passes. The first one is set to:

    ZTest Always
    ZWrite True
    ColorMask 0


    This means it doesn't test against the depth buffer, but still writes to the depth buffer, and only the depth buffer. The extra bit of magic is in the vertex shader you set the output clip space position to be at the far plane.

    Code (csharp):
    1. float4 vert (float4 vertex : POSITION) : SV_Position
    2. {
    3.     float4 pos = UnityObjectToClipPos(vertex);
    4.  
    5.     // borrowed from Aras's blog post on "infinite sky"
    6.     // http://aras-p.info/blog/2019/02/01/Infinite-sky-shader-for-Unity/
    7.  
    8.     #if defined(UNITY_REVERSED_Z)
    9.     // when using reversed-Z, make the Z be just a tiny
    10.     // bit above 0.0
    11.     pos.z = 1.0e-9f;
    12.     #else
    13.     // when not using reversed-Z, make Z/W be just a tiny
    14.     // bit below 1.0
    15.     pos.z = pos.w - 1.0e-6f;
    16.     #endif
    17.  
    18.     return pos;
    19. }
    20.  
    21. void frag() {} // yes, you can have a fragment shader that does nothing
    The result is the area your mesh covers the depth buffer is effectively cleared. Your second pass is then just totally normal. No need for ZTest Always as the previous pass put a big hole in the depth buffer for the second pass to render into.
     
  3. ShaderRig

    ShaderRig

    Joined:
    Feb 26, 2019
    Posts:
    18
    Thank you for the reply bgolus (I'm a big fan).

    Adding the second pass with "ZTest Always, ZWrite On and ColorMask 0" certainly got things moving forward.

    I'm still getting tripped up on Caveat 2. When my arrows go transparent, they are causing some issues. I wasn't expecting this but as they go transparent they do render correctly with the opaque geometry, which is great (I was expeting it to cut an arrow shaped hole in the object). The issue now is they do cut arrow shaped holes in the other arms of the gizmo when they are transparent.

    I also had to add an extra pass between so the alpha'ed gizmo would sort correctly.

    Code (CSharp):
    1. Shader "Custom/Gizmo"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset]_Mask ("Axis Mask", 2D) = "white" {}
    6.  
    7.         [Header(Colour)]
    8.         _XColour("X-Axis Colour", Color) = (1,1,1,1)
    9.         _YColour("Y-Axis Colour", Color) = (1,1,1,1)
    10.         _ZColour("Z-Axis Colour", Color) = (1,1,1,1)
    11.         _UniSColour("Uniform Scale Colour", Color) = (1,1,1,1)
    12.  
    13.         [Header(Emissive)]
    14.         _XEmissive("X-Axis Emissive", Color) = (1,1,1,1)
    15.         _YEmissive("Y-Axis Emissive", Color) = (1,1,1,1)
    16.         _ZEmissive("Z-Axis Emissive", Color) = (1,1,1,1)
    17.         _UniSEmissive("Uniform Scale Emissive", Color) = (1,1,1,1)
    18.  
    19.         [Header(Alpha)]
    20.         _XAxisAlpha("X-Axis Alpha", Range(0,1)) = 0.0
    21.         _YAxisAlpha("Y-Axis Alpha", Range(0,1)) = 0.0
    22.         _ZAxisAlpha("Z-Axis Alpha", Range(0,1)) = 0.0
    23.         _UniSAlpha("Uniform Scale Alpha", Range(0,1)) = 0.0
    24.  
    25.     }
    26.     SubShader
    27.     {
    28.         Tags { "RenderType"="Transparent" "Queue"="Transparent+10"}
    29.         LOD 150
    30.  
    31.         Pass{
    32.             ZTest Always
    33.             ZWrite On
    34.             ColorMask 0          
    35.             /*                      
    36.             CGPROGRAM
    37.             #pragma vertex vert
    38.             #pragma fragment frag
    39.        
    40.                 float4 vert(float4 vertex : POSITION) : SV_Position
    41.                 {
    42.                     float4 pos = UnityObjectToClipPos(vertex);
    43.  
    44.                     // borrowed from Aras's blog post on "infinite sky"
    45.                     // http://aras-p.info/blog/2019/02/01/Infinite-sky-shader-for-Unity/
    46.  
    47.                     #if defined(UNITY_REVERSED_Z)
    48.                     // when using reversed-Z, make the Z be just a tiny
    49.                     // bit above 0.0
    50.                     pos.z = 1.0e-9f;
    51.                     #else
    52.                     // when not using reversed-Z, make Z/W be just a tiny
    53.                     // bit below 1.0
    54.                     //pos.z = pos.w - 1.0e-6f;
    55.                     pos.z = pos.w - 1.0e-6f;
    56.                     #endif
    57.  
    58.                     return pos;
    59.                 }
    60.                
    61.                     void frag() {}
    62.             ENDCG
    63.             */
    64.         }
    65.        
    66.         Pass{
    67.             ColorMask 0
    68.             ZWrite On
    69.         }
    70.  
    71.         CGPROGRAM
    72.         #pragma surface surf Standard alpha:fade noshadow noambient nolightmap nofog
    73.         #pragma target 3.0
    74.  
    75.             sampler2D _Mask;
    76.  
    77.             half _Glossiness;
    78.             half _Metallic;
    79.  
    80.             fixed4 _XColour;
    81.             fixed4 _YColour;
    82.             fixed4 _ZColour;
    83.             fixed4 _UniSColour;
    84.  
    85.             fixed4 _XEmissive;
    86.             fixed4 _YEmissive;
    87.             fixed4 _ZEmissive;
    88.             fixed4 _UniSEmissive;
    89.  
    90.             half _spec;
    91.  
    92.             half _XAxisAlpha;
    93.             half _YAxisAlpha;
    94.             half _ZAxisAlpha;
    95.             half _UniSAlpha;
    96.  
    97.             struct Input
    98.             {
    99.                 float2 uv_Mask;
    100.             };
    101.             void surf (Input IN, inout SurfaceOutputStandard o)
    102.             {
    103.                 fixed4 c = tex2D (_Mask, IN.uv_Mask);
    104.  
    105.                 //Gizmo Colour
    106.                 half4 xColour = lerp(0, _XColour, c.r);
    107.                 half4 yColour = lerp(0, _YColour, c.g);
    108.                 half4 zColour = lerp(0, _ZColour, c.b);
    109.                 half4 uniSColour = lerp(0, _UniSColour, (1 - c.a));
    110.  
    111.                 half4 axisColourResult = xColour + yColour + zColour + uniSColour;
    112.  
    113.                 o.Albedo = axisColourResult;
    114.  
    115.                 //Gizmo Emissive
    116.                 half4 xEmissive = lerp(0, _XEmissive, c.r);
    117.                 half4 yEmissive = lerp(0, _YEmissive, c.g);
    118.                 half4 zEmissive = lerp(0, _ZEmissive, c.b);
    119.                 half4 uniSEmissive = lerp(0, _UniSEmissive, (1 - c.a));
    120.  
    121.                 half4 axisEmissiveResult = xEmissive + yEmissive + zEmissive + uniSEmissive;
    122.  
    123.                 o.Emission = axisEmissiveResult;
    124.  
    125.                 //Gizmo Alpha
    126.                 fixed3 xfade = lerp(1, 0, (c.r * _XAxisAlpha));
    127.                 fixed3 yfade = lerp(1, 0, (c.g * _YAxisAlpha));
    128.                 fixed3 zfade = lerp(1, 0, (c.b * _ZAxisAlpha));
    129.                 fixed3 uniSfade = lerp(1, 0, ((1 - c.a) * _UniSAlpha));
    130.  
    131.                 fixed3 axisAlphaResult = xfade * yfade * zfade * uniSfade;
    132.  
    133.                 o.Alpha = axisAlphaResult;
    134.             }
    135.             ENDCG
    136.     }
    137.     FallBack "Diffuse"
    138. }
    upload_2019-3-3_15-2-28.png
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Technically they are cutting a hole in the object, but only in the depth buffer and not the already rendered color buffer.

    To understand what's happening, you probably need to understand the depth buffer better. This article goes into good detail on the topic:
    https://simonschreibt.de/gat/black-flag-waterplane/
    https://data.simonschreibt.de/gat064/depthbuffer_example.webm

    The basic idea is the depth buffer is generally used to help sort opaque geometry, and prevent transparent geometry from rendering over opaque geometry that has been drawn before it. We're messing with that by blowing away the depth buffer, and then in your new shader's case rendering the depth of otherwise transparent geometry. When you render the color for your arrows, it's testing its z depth against itself, so it'll only draw where it was the closest to the camera.

    But why were you having problems before you added that second ColorMask 0 pass? Because efficient and correct sorting of real time transparent geometry is an unsolved problem.
    https://forum.unity.com/threads/render-mode-transparent-doesnt-work-see-video.357853/#post-2315934

    If you have ZWrite On enabled for a transparent shader, it doesn't prevent the triangles from drawing out of order, it just prevents triangles that are drawn later from drawing over triangles that were previously drawn closer. So in the case of triangles that are correctly sorted for transparency to the current view (back to front, aka Painter's Algorithm), the depth buffer isn't actually causing anything to be different as each triangle being drawn will be closer than the previous one. If the triangles are in the wrong order (front to back), then if they write to the depth (ZWrite On) later the pixels of triangles will be clipped if they're behind the previous triangles, or if you don't write to the depth (ZWrite Off) later triangles simply draw on top. But sorting triangles is expensive.

    If you want to be able to see the arrows through each other, you'll want to render each arrow of the gizmo separately so they can be sorted individually. For the image above, using the existing shader, that would mean you'd be able to see the blue arrow through the red one as you would expect, but the shaft of the red arrow would still be occluded by the head. The other option would be to use an additive blend mode so the draw order is irrelevant.
     
  5. ShaderRig

    ShaderRig

    Joined:
    Feb 26, 2019
    Posts:
    18
    Bit of a delay but! Thank you very much for the information bgolus. Very useful.