Search Unity

Question Shader with ZTest set as Always, but I need the Mesh to occlude itself

Discussion in 'Shaders' started by Chuddubs, Nov 17, 2021.

  1. Chuddubs

    Chuddubs

    Joined:
    Jun 30, 2020
    Posts:
    7
    Using URP on Unity version 2020.3.5f
    Hello ! I still have a lot to learn about shaders, and I need some insight from more experienced people.
    I have a single Mesh made from several cubes that overlap themselves:
    overlappingMesh.PNG

    I want this Mesh to be rendered on top of all other geometry, so I made a custom shader and set ZTest to Always. I also set ZWrite to On. However, now the parts of the mesh that are behind the others are also drawn... which makes sense since the shader says everything with this Material should be rendered, regardless of distance.

    With ZTest LEqual:
    ZTest_LEqual.PNG

    With ZTest Always:
    ZTest_Always.PNG

    As you can see in the last image the smaller cubes in the top-left corner are rendered on top of the larger one. Pay no attention to the "shadows" in these screenshots: they're not real shadows but merely part of the texture.
    Is there a way I can render this mesh on top of every other geometry excluding its own geometry?
    Do I need to split my mesh in two (i.e. foreground and background) ?
    Thanks in advance and let me know if this post is poorly formatted. I'm much more used to lurking in existing threads rather than creating my own.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Yes and no.

    Something like this is an option. I'll explain further below.

    First a question: Will this set of cubes always be seen from roughly the same direction, i.e. you won't be able to see this from the back?

    If so the "easiest" solution is to manually sort the elements of the mesh so those that are "in the back" are drawn first. This can be done in most 3D software by separating the elements into different meshes, then, starting with the "furthest back" element as the parent, append / merge them back into one mesh one by one in the order you want them to draw (back to front).

    The depth buffer (aka z buffer) is normally used to sort opaque surfaces so that they are always properly sorted regardless of the order they draw in. This works by each surface calculating its screen space depth at each pixel, comparing that to the depth in the z buffer, and then either skipping rendering that surface to pixels that have a depth closer to the camera, or rendering and updating the depth in the z buffer to the current.
    ZWrite
    and
    ZTest
    let you change the behavior for if it overwrites the value in the depth value on passing the test, and how or if it tests. But this only works for opaque objects. Transparent objects can test against the depth, but writing to depth does not guarantee a correctly sorted final image. If a partially transparent object is rendered, then another object is rendered further away from the previous one, there's no way to render the new object behind what has already been rendered without knowing what the color & blend mode of the previous surface was. So instead transparent objects are pre-sorted by the CPU as well as they can be and then rendered back to front. This is known as the Painter's Algorithm.

    The reason I explained all that is that's what you'd be wanting to do here. Sort them back to front. And because it's a single mesh that's something you'd have to do yourself manually before importing it into Unity, or write a script to sort the triangles yourself after the fact.


    There is another option though. The other option would be a two pass shader.

    Back to your first question, it's not possible to make a mesh render on top of every other object excluding its own geometry. Not in a single pass. But you can use a pass set to
    ZTest Always
    and with an overridden clip space .z to punch a hole in the depth buffer that allows subsequent passes to render normally into.

    Put this in your shader before the "main" pass(es):
    Code (csharp):
    1. Pass {
    2.     ZTest Always // render over everything
    3.     ZWrite On // still render to the depth (default)
    4.     ColorMask 0 // don't render to color, only depth and/or stencil
    5.  
    6.     CGPROGRAM
    7.     #pragma vertex vert
    8.     #pragma fragment frag
    9.  
    10.     float4 vert (appdata_full v) : SV_Position
    11.     {
    12.         float4 clipPos = UnityObjectToClipPos(v.vertex);
    13.     #if UNITY_REVERSED_Z
    14.         clipPos.z = 0.000001; // the far clip plane is at 0.0 in everything not OpenGL
    15.     #else
    16.         clipPos.z = clipPos.w * 0.999999; // in OpenGL / GLES the far clip is at "1.0", or the w value in clip space.
    17.     #end
    18.         return clipPos;
    19.     }
    20.  
    21.     // fragment shader doesn't need to do anything, but is still needed or Unity gets upset
    22.     fixed4 frag () : SV_Target { return 0; }
    23.     ENDCG
    24. }
    Then set your other passes to use
    ZTest LEqual
    , or remove the
    ZTest
    line from them as that's the default.
     
    zhuhaiyia1 and JollyTheory like this.
  3. Chuddubs

    Chuddubs

    Joined:
    Jun 30, 2020
    Posts:
    7
    Thank you so much bgolus ! I was trying to wrap my head around the z-punching method I read about in another thread (I think it was one of your posts as well) but you just made the whole thing clearer for me.
    The mesh was supposed to be seen from same angle every time, so I could have gone with your first solution, but i ended up making an useful shader that I can reuse for the same purpose, thanks to the snippet you posted.
    Just had to add the obvious #include "UnityCG.cginc" to the shader and enable "Shared Depth Buffer" for the XR Oculus Plugin in my Project Settings, but other than that you pretty much handed me the solution on a silver platter.

    Just wanted to let you know again that your help is really appreciated, I learn a lot whenever I come across one of your posts on this forum. So thanks for taking the time to explain everything.
     
  4. OrbitalDuck

    OrbitalDuck

    Joined:
    Oct 26, 2013
    Posts:
    60
    I recently updated to unity 2021 and the URP where I had the implemented thid by just adding a pass with

    ZTest Always
    ZWrite On

    before the rest of the unlit shader but its stopped working in the URP.

    Code (CSharp):
    1. Shader "Unlit/Unlit Color Overlay"
    2. {
    3.    Properties {
    4.     _Color ("Main Color", Color) = (1,1,1,1)
    5. }
    6.  
    7. SubShader {
    8.      Tags { "RenderType"="Opaque" "Queue" = "Geometry+1"}
    9.      LOD 200
    10.  
    11.        Pass {
    12.          ZTest Always
    13.          ZWrite On
    14.          }
    15.  
    16.  
    17.     Pass {
    18.  
    19.          ZTest LEqual
    20.          ZWrite On
    21.          CGPROGRAM
    22.  
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.             #pragma target 2.0
    26.            // #pragma multi_compile_fog
    27.  
    28.             #include "UnityCG.cginc"
    29.  
    30.             struct appdata_t {
    31.                 float4 vertex : POSITION;
    32.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    33.             };
    34.  
    35.             struct v2f {
    36.                 float4 vertex : SV_POSITION;
    37.                // UNITY_FOG_COORDS(0)
    38.                 UNITY_VERTEX_OUTPUT_STEREO
    39.             };
    40.  
    41.             fixed4 _Color;
    42.  
    43.             v2f vert (appdata_t v)
    44.             {
    45.                 v2f o;
    46.                 UNITY_SETUP_INSTANCE_ID(v);
    47.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    48.                 o.vertex = UnityObjectToClipPos(v.vertex);
    49.                // UNITY_TRANSFER_FOG(o,o.vertex);
    50.                 return o;
    51.             }
    52.  
    53.             fixed4 frag (v2f i) : COLOR
    54.             {
    55.                 fixed4 col = _Color;
    56.                // UNITY_APPLY_FOG(i.fogCoord, col);
    57.                 UNITY_OPAQUE_ALPHA(col.a);
    58.                 return col;
    59.             }
    60.         ENDCG
    61.     }
    62.  
    63.   }
    64. }

    Ive tried to update it with this but cant get it to work. I have this but it gives some errors around the UNITY_REVERSED_Z . did a bit of commenting it out to see if It could work but it just hide everything/renderout out black

    Code (CSharp):
    1. Shader "Universal Render Pipeline/Unlit Overlay"
    2. {
    3.     Properties{
    4.        _Color("Main Color", Color) = (1,1,1,1)
    5.     }
    6.  
    7.     SubShader{
    8.  
    9.      Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
    10.      LOD 200
    11.  
    12.         Pass {
    13.  
    14.         ZTest Always // render over everything
    15.         ZWrite On // still render to the depth (default)
    16.         ColorMask 0 // don't render to color, only depth and/or stencil
    17.  
    18.         CGPROGRAM
    19.         #pragma vertex vert
    20.         #pragma fragment frag
    21.         #include "UnityCG.cginc"
    22.  
    23.         float4 vert(appdata_full v) : SV_Position
    24.         {
    25.             float4 clipPos = UnityObjectToClipPos(v.vertex);
    26.  
    27.          #if UNITY_REVERSED_Z
    28.  
    29.           clipPos.z = 0.000001; // the far clip plane is at 0.0 in everything not OpenGL
    30.  
    31.          #else
    32.  
    33.            clipPos.z = clipPos.w * 0.999999; // in OpenGL / GLES the far clip is at "1.0", or the w value in clip space.
    34.          #end
    35.  
    36.             return clipPos;
    37.         }
    38.  
    39.         // fragment shader doesn't need to do anything, but is still needed or Unity gets upset
    40.         fixed4 frag() : SV_Target { return 0; }
    41.         ENDCG
    42.     }
    43.  
    44.  
    45.      Pass
    46.         {
    47.             HLSLPROGRAM
    48.             #pragma vertex vert
    49.             #pragma fragment frag
    50.  
    51.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    52.  
    53.             struct Attributes
    54.             {
    55.                 float4 positionOS   : POSITION;
    56.             };
    57.  
    58.             struct Varyings
    59.             {
    60.                 float4 positionHCS  : SV_POSITION;
    61.             };
    62.  
    63.             // To make the Unity shader SRP Batcher compatible, declare all
    64.             // properties related to a Material in a a single CBUFFER block with
    65.             // the name UnityPerMaterial.
    66.             CBUFFER_START(UnityPerMaterial)
    67.                 // The following line declares the _BaseColor variable, so that you
    68.                 // can use it in the fragment shader.
    69.                 half4 _BaseColor;
    70.             CBUFFER_END
    71.  
    72.             Varyings vert(Attributes IN)
    73.             {
    74.                 Varyings OUT;
    75.                 OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
    76.                 return OUT;
    77.             }
    78.  
    79.             half4 frag() : SV_Target
    80.             {
    81.                 // Returning the _BaseColor value.
    82.                 return _BaseColor;
    83.             }
    84.             ENDHLSL
    85.         }
    86.  
    87.     }
    88.  
    89.  
    90. }
    91.  


    shaders are something I find so difficult to understand and any help getting it to work would be very much appreciated.
     
  5. MrDizzle26

    MrDizzle26

    Joined:
    Feb 8, 2015
    Posts:
    36

    I tried getting this to work in URP with a lit particle shader and no dice, how would this look with a URP lit particle shader with vertex color passed through?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
  7. JollyTheory

    JollyTheory

    Joined:
    Dec 30, 2018
    Posts:
    156
    An improvement: write 0 into depth from frag shader instead of vertex one, otherwise it produces NaN's and stuff on 4x4 pixel groups (at least on metal)
    Code (CSharp):
    1. Pass
    2. {
    3.     ZTest Always // never fail depth test
    4.     ZWrite On // render to the depth
    5.     ColorMask 0 // don't render to color, only depth and/or stencil
    6.  
    7.     CGPROGRAM
    8.     #pragma vertex vert
    9.     #pragma fragment frag
    10.  
    11.     float4 vert(float4 vertex : POSITION) : SV_Position
    12.     {
    13.         return UnityObjectToClipPos(vertex);
    14.     }
    15.  
    16.     float frag() : SV_Depth
    17.     {
    18.         return 0.0;
    19.     }
    20.     ENDCG
    21. }