Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Unity 2022.2 is now available as the latest Tech release.
    Dismiss Notice
  3. We are making some changes to the DOTS forums.
    Dismiss Notice
  4. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

Question about ZWrite and transparency

Discussion in 'Shaders' started by meteorstorm, Apr 30, 2018.

  1. meteorstorm


    Mar 26, 2018
    Hey guys,
    I'm learning shader coding for the first time and ran into an issue I don't quite understand.
    I'm trying to create a hologram shader.
    After I apply it to multiple meshes in my object, backface culling works, but slight viewDir changes make background / inner meshes of the object pop in and out.

    1.PNG 2.PNG

    Any insight on what causes that?
    I'd think ZWrite On would either (A) make the farther z-values not get rendered, or (B) due to transparency, they'd always be rendered over each other.
    I don't understand the popping :(

    My shader code is pretty simple:

    Code (CSharp):
    1. Shader "Custom/Hologram" {
    2.     Properties {
    3.         _RimColor ("Rim Color", Color) = (1,1,1,1)
    4.         _RimPower ("Rim Power", Range(0.5,8)) = 3
    5.     }
    7.     SubShader {
    8.         Tags { "Queue"="Transparent" }
    10.         Pass {
    11.             ZWrite On            // write depth data to z-buffer
    12.             ColorMask 0            // but won't write color to frame buffer
    13.         }
    14.         CGPROGRAM
    16.         #pragma surface surf Lambert alpha:fade
    18.         float4 _RimColor;
    19.         float _RimPower;
    21.         struct Input {
    22.             float3 viewDir;
    23.         };
    25.         void surf (Input IN, inout SurfaceOutput o) {
    26.             half dotp = dot(normalize(IN.viewDir), o.Normal);
    27.             half rim = 1 - saturate(dotp);
    28.             float rimPow = pow(rim,_RimPower);
    29.             o.Emission = _RimColor.rgb * rimPow;
    30.             o.Alpha = rimPow;
    31.         }
    32.         ENDCG
    33.     }
    34.     FallBack "Diffuse"
    35. }
  2. mgear


    Aug 3, 2010
  3. bgolus


    Dec 7, 2012
    Because transparent meshes are only sorted by mesh, not per pixel like opaque objects. With out the ZWrite pass in the most basic case the meshes are sorted roughly far to near by their bounds, but also by material, and maybe by mesh if instanced. After that the mesh’s tris are drawn in the order they are stored in the mesh.

    You can read my more in depth post on it here:

    Otherwise, you can read the link @mgear posted where I went into possibly work arounds and solutions.

    One additional note about using the ZWrite pass in the shader and multiple meshes. If you use the material on multiple meshes, Unity may choose to draw the first pass of your shader for all of those meshes before drawing them again with the second pass. It may also choose not to do this, instead drawing both passes for one mesh before drawing the next mesh. Or it may do a mix of both. For the ZWrite pass to work properly on complex multi part model like this it has to render that first pass for all objects before the second, otherwise you may have something like a wheel render before that area has been “hidden” by the depth buffer. To force this to happen it’s best to use this technique only on single meshes at a time, or combine complex meshes into one mesh object with only one material index. Otherwise you’ll need to split up the shader into two separate shaders and force the rendering order with the render queue, rendering the meshes twice either by assigning an additional material index to meshes that only have one material, or duplicating the mesh renderers.
  4. meteorstorm


    Mar 26, 2018
    Appreciate the replies guys! Please bear with my noobishness :)

    @mgear, yep, setting sort mode to custom does away with the popping, but I need to look into this mode more to understand how it sorts, as it still looks a bit odd (some internal geometry is visible, some isn't).

    @bgolus, ok I'm starting to see the problem after reading those. (Didn't realize it was a sort of unsolved problem, thought I was just messing it up.)

    Correct me if I'm wrong, but solutions mentioned want to show all the transparent geometry? Would it be possible to get something similar to this effect, but without rendering the background meshes? Like don't show any geometry that wouldn't be visible if this were opaque?

    In the other thread @bgolus, you mention having transparent objects write to the depth buffer. I thought that's what this was supposed to do:
    Code (CSharp):
    1. Pass {
    2.     ZWrite On
    3.     ColorMask 0
    4. }
    I'd think that would mean most of the back wheel wouldn't render.

    If that approach is used for something like transparent HUDs, I guess the devs have to assume there are no transparent objects behind?
    I'm guessing this was likely done with another approach, like offscreen buffer or ... well, actually it's just a quad, so sorting doesn't matter? :)
  5. bgolus


    Dec 7, 2012
    It all depends on the draw order. See the last paragraph of my previous post. Also try using the frame debugger to step through rendering. You'll see something like this:

    Render both passes of back right wheel.
    Render both passes of front right wheel.
    Render both passes of body.
    Render both passes of back left wheel.
    Render both passes of front left wheel.

    The problem is the transparent passes for the left wheels have already rendered by the time the body's ZWrite pass renders, and because of that they weren't hidden by the body's depth buffer. For the back wheels to be hidden the Zwrite only passes for all four wheels, the body, and anything else needs to render before the transparent passes of anything else.

    Rendering it to an off screen buffer is a common way to do this, but not the only way. For UI work you are usually assuming it's being rendered last after everything else in the scene had already rendered, and the render order of UI elements is also usually explicitly controlled and known before hand. Thus the auto rendering order issues you're seeing don't usually cause a problem.
  6. meteorstorm


    Mar 26, 2018
    Thanks for elaborating :) Didn't stick the first time lol, but yeah, that makes sense now.

    Nice, so I can force all of them to render to the depth buffer first with a multi-shader approach. I may give that a shot soon! Curious about the fps hit with the additional pass.

    Ok cool. I'll tinker around with off-screen buffers later I guess, don't want to bite off too much at once.
    Really appreciate the patience & help!!