0

My 3D object is overlapping itself with alpha when using a custom shader in Unity3D (Unlit version):

Screenshot1

It should look something like this instead:

Screenshot2

Shader "Custom/Shader1" {
    Properties {
     _Color ("Main Color", Color) = (1,1,1,1)
     _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
     _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
    }

    SubShader {
     Tags { "Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True" }
     Pass {
         ZWrite On
         ColorMask 0
     }
     Pass {
      ZWrite Off // don't write to depth buffer 
      Blend SrcAlpha OneMinusSrcAlpha // use alpha blending
      CGPROGRAM

      #pragma vertex vert
      #pragma fragment frag

      uniform float4 _Color; // define shader property for shaders
      uniform sampler2D _MainTex;
      uniform float _Cutoff;

      struct vertexInput {
       float4 vertex : POSITION;
       float2 texcoord : TEXCOORD0;
      };
      struct vertexOutput {
       float4 pos : SV_POSITION;
       float2 tex : TEXCOORD0;
      };

      vertexOutput vert(vertexInput input) {
       vertexOutput output;

       output.tex = input.texcoord;
       output.pos = UnityObjectToClipPos(input.vertex);
       return output;
      }

      float4 frag(vertexOutput input) : COLOR {
       float4 col = tex2D(_MainTex, input.tex) * _Color;    
       float newOpacity = 1.0;
       if (col.a < _Cutoff) {
         newOpacity = 0.0;
       }
       return float4(col.r, col.g, col.b, newOpacity);
      }
      ENDCG
     }
    }
   }

Did I miss anything? It seems like the transparency alpha overlaps itself.

Edit 1 I removed the first pass, then enabled Zbuffer and removed the if (col.a < _Cutoff) and let it be dynamic according to its texture, but I still get the same result like the 1st image.

Shader "Custom/Shader1" {
    Properties {
     _Color ("Main Color", Color) = (1,1,1,1)
     _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
     _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
    }

    SubShader {
     Tags { "Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True" }
     Pass {
      ZWrite On
      Blend SrcAlpha OneMinusSrcAlpha // use alpha blending
      CGPROGRAM

      #pragma vertex vert
      #pragma fragment frag

      uniform float4 _Color; // define shader property for shaders
      uniform sampler2D _MainTex;
      uniform float _Cutoff;

      struct vertexInput {
       float4 vertex : POSITION;
       float2 texcoord : TEXCOORD0;
      };
      struct vertexOutput {
       float4 pos : SV_POSITION;
       float2 tex : TEXCOORD0;
      };

      vertexOutput vert(vertexInput input) {
       vertexOutput output;

       output.tex = input.texcoord;
       output.pos = UnityObjectToClipPos(input.vertex);
       return output;
      }

      float4 frag(vertexOutput input) : COLOR {
       float4 col = tex2D(_MainTex, input.tex) * _Color;    
       return col;
      }
      ENDCG
     }
    }
   }
kkl
  • 153
  • 2
  • 12

1 Answers1

2
  1. The shader is unlit because you use a regular CG shader instead of a Surface shader, whose purpose is to conveniently provide lighting behind the scene. Solution: start fresh from a surface shader template
  2. Alpha blending is not taken advantage of since(col.a < _Cutoff) is always either true or false with no intermediate values, it's always fully shown or fully hidden. On top of that the way you write that condition probably generates some dynamic branching in the shader, try static branching instead float newOpacity = (col.a < _Cutoff) ? 0.0 : 1.0; (in that case the executed code is always the same, only the value changes which is usually much better for perfs).
  3. The shader has 2 passes, which will prevent batching later on and that is pretty bad for performances. One is filling the Zbuffer and the other is doing alpha blending (2 operations that are not really compatible). The prepass is not working properly because it fills the buffer with the raw mesh, without having the information of transparency coming from the texture. Sure you could modify the first pass to do that, but the outcome you look for is essentially just a very straighforward Surface shader with alpha testing on.

You can read on the semantics exposed by Unity to control alpha testing in Surface shaders here: https://docs.unity3d.com/Manual/SL-SurfaceShaders.html , specifically that part :

alphatest:VariableName - Enable alpha cutout transparency. Cutoff value is in a float variable with VariableName. You’ll likely also want to use addshadow directive to generate proper shadow caster pass.

alternatively you can use the clip() method in combination with addshadow, most likely along the lines of clip(col.a - 0.5);

Brice V.
  • 861
  • 4
  • 7
  • Hi @Brice, 1. Surface shader has big performance cost in mobile [reference](https://www.youtube.com/watch?v=3penhrrKCYg&feature=youtu.be&t=3064). That's why I stick with regular shader. 2. How to code to take advantage of alpha blending in regular shader? Would be great if you have example I can refer =) 3. I use 2 passes becoz the order of rendering seems wrong if I dun write to zbuffer as mentioned here [link](https://docs.unity3d.com/Manual/SL-CullAndDepth.html). How do we solve this? – kkl Jun 07 '18 at 04:05
  • I did abit study previously and clip() function and alpha testing are costly in mobile since they can't take advantage of deferred rendering. 2 passes is better than alpha testing, as mentioned [here](https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/Performance/Performance.html) under "Use Hidden Surface Removal Effectively" – kkl Jun 07 '18 at 04:05
  • I updated my post with new code according to your suggestion, but got the same issue – kkl Jun 07 '18 at 04:30
  • Hi. You failed to mention anything about mobile or deferred in your question, and in that case you are right that it has a substantial impact :) It all depends on your use case, which I know nothing about unfortunately. Transparency and sorting is a big topic, there are no one-fits-all solution. – Brice V. Jun 11 '18 at 16:03
  • Hi @Brice, sry for not mentioning mobile. I updated the title. Do you have any idea where I can look for the different solutions to fit my problem? – kkl Jun 17 '18 at 10:45
  • When dealing with arbitrary concave objects and transparency you don't really have a choice/good solution. And, again, I don't know about your context here, you game is probably not about a single leaf in a gray scene. The generic solution _is_ to use alpha testing. If performances are bad on your target, profile and design around it. If your problem is an edge case, like a specific asset, then you could maybe get away with it by dealing with single sided passes and the draw order. – Brice V. Jun 17 '18 at 12:58
  • Do you mean separating the leaf into 2 objects (top side and bottom side), then draw the top after drawing the bottom? It's a great idea and I think that might work. Let me try that out. Not sure if 2 draw calls yield more performance cost than 1 draw call with alpha testing though. – kkl Jun 17 '18 at 13:24