Transparent shader with Texture and Normal Map

Sup, folks.
I’ve got quite a conundrum on my hands.
What I’m trying to achieve is: semi-transparent material with texture and normal map.

I’m using this shader:

Shader "Cg normal mapping" {
    Properties{
        _MainTex("Texture", 2D) = "white" {}
       _BumpMap("Normal Map", 2D) = "bump" {}
       _Color("Diffuse Material Color", Color) = (1,1,1,1)
       _SpecColor("Specular Material Color", Color) = (1,1,1,1)
       _Shininess("Shininess", Float) = 10
        _MinVisDistance("MinDistance",Float) = 0
        _MaxVisDistance("MaxDistance",Float) = 20
    }

     
CGINCLUDE

#include "UnityCG.cginc"
        uniform float4 _LightColor0;
       uniform sampler2D _BumpMap;
       uniform sampler2D _MainTex;
       uniform float4 _BumpMap_ST;
       uniform float4 _Color;
       uniform float4 _SpecColor;
       uniform float _Shininess;

       half        _MinVisDistance;
       half        _MaxVisDistance;

       struct vertexInput {
           float4 vertex : POSITION;
           float4 texcoord : TEXCOORD0;
           float3 normal : NORMAL;
           float4 tangent : TANGENT;
       };


       struct vertexOutput {
           float4 pos : SV_POSITION;
           float4 posWorld : TEXCOORD0;        
           float4 tex : TEXCOORD1;
           float3 tangentWorld : TEXCOORD2;
           float3 normalWorld : TEXCOORD3;
           float3 binormalWorld : TEXCOORD4;
       };

       vertexOutput vert(vertexInput input)
       {
           vertexOutput output;

           float4x4 modelMatrix = unity_ObjectToWorld;
           float4x4 modelMatrixInverse = unity_WorldToObject;

           output.tangentWorld = normalize(
               mul(modelMatrix, float4(input.tangent.xyz, 0.0)).xyz);
           output.normalWorld = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
           output.binormalWorld = normalize(
               cross(output.normalWorld, output.tangentWorld)
               * input.tangent.w);

           output.posWorld = mul(modelMatrix, input.vertex);
           output.tex = input.texcoord;
           output.pos = UnityObjectToClipPos(input.vertex);
        
           return output;
       }

    
       float4 fragWithAmbient(vertexOutput input) : COLOR
       {
           /*half2 uv_BumpMap = TRANSFORM_TEX(input.tex, _BumpMap);
           half3 viewDirW = _WorldSpaceCameraPos - mul((half4x4)unity_ObjectToWorld, input.pos);
           half viewDist = length(viewDirW);
           half falloff = saturate((viewDist - _MinVisDistance) / (_MaxVisDistance - _MinVisDistance));    */    



           float4 encodedNormal = tex2D(_BumpMap,
              _BumpMap_ST.xy * input.tex.xy + _BumpMap_ST.zw);
           float3 localCoords = float3(2.0 * encodedNormal.a - 1.0,
               2.0 * encodedNormal.g - 1.0, 0.0);
           localCoords.z = sqrt(1.0 - dot(localCoords, localCoords));
      

           float3x3 local2WorldTranspose = float3x3(
              input.tangentWorld,
              input.binormalWorld,
              input.normalWorld);
           float3 normalDirection =
              normalize(mul(localCoords, local2WorldTranspose));

           float3 viewDirection = normalize(
              _WorldSpaceCameraPos - input.posWorld.xyz);
           float3 lightDirection;
           float attenuation;

           if (0.0 == _WorldSpaceLightPos0.w)
           {
              attenuation = 1.0;
              lightDirection = normalize(_WorldSpaceLightPos0.xyz);
           }
           else
           {
              float3 vertexToLightSource =
                 _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
              float distance = length(vertexToLightSource);
              attenuation = 1.0 / distance;
              lightDirection = normalize(vertexToLightSource);
           }

           float3 ambientLighting =
              UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

           float3 diffuseReflection =
              attenuation * _LightColor0.rgb * _Color.rgb
              * max(0.0, dot(normalDirection, lightDirection));

           float3 specularReflection;
           if (dot(normalDirection, lightDirection) < 0.0)
          
            {
               specularReflection = float3(0.0, 0.0, 0.0);
            
            }
            else
            {
               specularReflection = attenuation * _LightColor0.rgb
                  * _SpecColor.rgb * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection),
                  viewDirection)), _Shininess);
            }        

            return float4((ambientLighting + diffuseReflection
               + specularReflection), 1);
       }

           ENDCG

           SubShader {
           Pass{
               ZWrite Off
               Cull Back
               Blend SrcAlpha OneMinusSrcAlpha
              Tags { "LightMode" = "ForwardBase" "RenderType" = "Transparent" }

           CGPROGRAM

              #pragma vertex vert
              #pragma fragment fragWithAmbient
            ENDCG
           }

       }
}

It works perfect except for this part:


As you can see some of the faces are darker than the other mesh and even looks like they are drawn “in front” of other parts.
Numerous surface and fragments shaders yeld the same result, even the standard one with transparency and texture/normal set:

Geometry is a basic volumetric mesh:5269911--527586--Geometry.png

Am I missing something dramaticly here? Can this be achieved with one shader without mesh splitting?

Efficient, and correct sorting of 3d transparency in real time rendering is still an unsolved problem.

Mesh splitting with the mesh you have wouldn’t work 100% of the time either due to how mesh sorting is done, and you’d have to break it up into individual triangles to handle the dark edge on the right in the first image.

Usual solutions are to use dithered transparency, or a depth only pass. Both of these solutions will generally mean that overlapping faces are hidden as if the mesh was opaque rather than appearing through each other.

Add this line to your shader between the existing SubShader and Pass lines:

Pass { ColorMask 0 ZWrite On }

Beyond that would be implementing some form of order independent transparency, like Weighted Blend or depth peeling, both of which are non-trivial to get working well and fast in Unity and require a bit of c# code and scene setup. There are example projects you can find if you search online.

@bgolus thanks for the information. I will take a look at those projects.

I’ve ended up using a quite simple surface shader with an added additional pass.

"Custom/FadeDistance2" {
    Properties{
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("BumpMap", 2D) = "bump" {}
        _MinColor("Color in Minimal", Color) = (1, 1, 1, 1)
        _MaxColor("Color in Maximal", Color) = (0, 0, 0, 0)
        _MinDistance("Min Distance", Float) = 1
        _MaxDistance("Max Distance", Float) = 10
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }
        SubShader{

            Pass{ ColorMask 0 ZWrite On }

            Tags { "RenderType" = "Transparent"  "Queue" = "Transparent"}
            LOD 200

            CGPROGRAM
            // Physically-based Standard lighting model, and enable shadows on all light types
            #pragma surface surf Standard alpha fullforwardshadows

            // Use shader model 3.0 target, to get nicer looking lighting
            #pragma target 3.0

            sampler2D _MainTex;
            sampler2D _BumpMap;

            struct Input {
                float2 uv_MainTex;
                float2 uv_BumpMap;
                float3 worldPos;
            };

            float _MaxDistance;
            float _MinDistance;
            half4 _MinColor;
            half4 _MaxColor;
            half _Glossiness;
            half _Metallic;

            void surf(Input IN, inout SurfaceOutputStandard o) {
                // Albedo comes from a texture tinted by color
                half4 c = tex2D(_MainTex, IN.uv_MainTex);
                float dist = distance(_WorldSpaceCameraPos, IN.worldPos);
                half weight = saturate((dist - _MinDistance) / (_MaxDistance - _MinDistance));
                half4 distanceColor = lerp(_MinColor, _MaxColor, weight);

                o.Albedo = c.rgb * distanceColor.rgb;
                o.Alpha = c.a * distanceColor.a;              
                o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));

                // Metallic and smoothness come from slider variables
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
            }
            ENDCG
        }
            FallBack "Diffuse"

Just one problem is left to solve: light falling on the bump map doesn’t disappear with transparency


I guess I have to edit unpacked normal somehow but I can’t quite wrap my head around it.
@bgolus could you push me in the right direction with this problem? Thanks in advance.

P.S. Sorry for the direct ping, I don’t know if it’s considered a bad manner on this forum.

The default alpha mode for surface shaders using the Standard shading model is a something akin to glass, where it’s transparent but still shows specular highlights and reflections. If you want it to fade out to being invisible, you need to use the alpha:fade option.

Thank you, @bgolus . It works like a charm!