Cutout shader with alpha blend

The documentation for Cutout shaders says that since the alpha channel of the main texture is used to determine the alpha cutoff for transparent areas, it’s impossible to then have partial transparency on the entire output. Is this still true, or is the documentation out of date?

There are so many cutout models out there (Mixamo makes lots of them, for example) that it seems unreasonable that we can’t ever fade our characters out (like a ghost) in our games.

I’ve been banging my head against the wall for the last two days trying to make a shader to do this successfully, but with no success thus far. That being said, I’m a shader noob, so I’m guessing one of you folks reading here might have better luck than I’ve had.

Currently I’m using the “Mixamo/Shadier” shader (which was recommended by Mixamo support, and was made by Charles Piña for their models). It supports glow maps, normal maps, specular maps, and a fresnel-based rim highlight, in addition to the “cutout” feature.

link - http://steamcommunity.com/sharedfiles/filedetails/?id=194204162

Shader "Mixamo/Shadier" {
Properties {
  _MainTex ("Base and Alpha (RGBA)", 2D) = "white" {}
  _Specular ("Specular", 2D) = "black" {}
  _SpecAmount ("Specular Amount", Range(0.0, 1.0)) = 0.0
  _BumpMap ("BumpMap", 2D) = "bump" {}
  _Emission ("Emission", 2D) = "black" {}
  _Color ("Main Color", Color) = (1,1,1,1)
  _RimColor ("Rim Color", Color) = (0.15, 0.15, 0.15, 0.0)
  _RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0
}

SubShader {
  Tags { "RenderType"="Transparent" "Queue"="Transparent"}
  LOD 200
  ZWrite On
  Cull Off
  AlphaTest GEqual 0.9
  Blend SrcAlpha OneMinusSrcAlpha


  CGPROGRAM
  #pragma surface surf SimpleBlinnPhong noforwardadd
  // #pragma target 3.0

  half4 LightingSimpleBlinnPhong (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
    half3 h = normalize (lightDir + viewDir);
    half diff = max (0, dot (s.Normal, lightDir));

    float nh = max (0, dot (s.Normal, h));
    float spec = pow (nh, s.Specular * 128.0) * s.Gloss;

    half4 c;
    c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
    c.a = s.Alpha;
    return c;
  }

  sampler2D _MainTex;
  sampler2D _Specular;
  sampler2D _Gloss;
  sampler2D _BumpMap;
  sampler2D _Emission;
  float4 _Color;
  float4 _RimColor;
  float _RimPower;
  float _GlossAmount;
  float _SpecAmount;

  struct Input {
    float2 uv_MainTex;
    float2 uv_Specular;
    float2 uv_Gloss;
    float2 uv_BumpMap;
    float2 uv_Emission;
    float3 viewDir;
  };

  void surf (Input IN, inout SurfaceOutput o) {
    half4 c = tex2D (_MainTex, IN.uv_MainTex);
    
        o.Albedo = c.rgb * _Color.rgb;
    o.Alpha = c.a;
    o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));
    o.Gloss = tex2D (_Specular, IN.uv_Specular).r;
    o.Specular = _SpecAmount;
    half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
    o.Emission = clamp(tex2D (_Emission, IN.uv_Emission).rgb + _RimColor.rgb * min(pow(rim, _RimPower), 0.9), half3 (0, 0, 0), half3 (1, 1, 1));
  }

  ENDCG


  // fringe alpha pass
  ZWrite Off
  ZTest Less
  AlphaTest Less 0.9
  Blend SrcAlpha OneMinusSrcAlpha

  CGPROGRAM
  #pragma surface surf SimpleBlinnPhong noforwardadd
  // #pragma target 3.0
  
  half4 LightingSimpleBlinnPhong (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
    half3 h = normalize (lightDir + viewDir);
    half diff = max (0, dot (s.Normal, lightDir));
  
    float nh = max (0, dot (s.Normal, h));
    float spec = pow (nh, s.Specular * 128.0) * s.Gloss;

    half4 c;
    c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
    c.a = s.Alpha;
    return c;
  }
  
  sampler2D _MainTex;
  sampler2D _Specular;
  sampler2D _Gloss;
  sampler2D _BumpMap;
  sampler2D _Emission;
  float4 _Color;
  float4 _RimColor;
  float _RimPower;
  float _GlossAmount;
  float _SpecAmount;
  
  struct Input {
    float2 uv_MainTex;
    float2 uv_Specular;
    float2 uv_Gloss;
    float2 uv_BumpMap;
    float2 uv_Emission;
    float3 viewDir;
  };
  
  void surf (Input IN, inout SurfaceOutput o) {
    half4 c = tex2D (_MainTex, IN.uv_MainTex);

    o.Albedo = c.rgb * _Color.rgb;
    o.Alpha = c.a;
    o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));
    o.Gloss = tex2D (_Specular, IN.uv_Specular).r;
    o.Specular = _SpecAmount;
    half rim = 0.0; // 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
    o.Emission = clamp(tex2D (_Emission, IN.uv_Emission).rgb + _RimColor.rgb * min(pow(rim, _RimPower), 0.9), half3 (0, 0, 0), half3 (1, 1, 1));
  }

  ENDCG
}

  FallBack "Bumped Diffuse"
}

Is it possible to modify this shader to add an end-stage alpha fader, that takes the final rendered output from the existing shader and simply allows it to be made partially transparent?

Thanks in advance for any insights/advice. :slight_smile:

you can control transparency on the transparent areas and the entire texture. if you devide the final alpha output by 2 for example, the opaque zones will also be 1/2 transparent.

Right, but I need to do that after the AlphaTest GEqual 0.9, so that the cutouts work properly. How can I apply such division AFTER that alpha test is done?

Instead of using alpha testing, you can use discard or clip to reject pixels and then apply the fade after that. (In the surface shader.)

clip(c.a - 0.9);
o.Alpha = c.a * fade;

Thanks for the suggestion. I’ve tried this just now and it didn’t have the desired result. I’m probably not explaining myself very well since I’m a shader noob.

What I want is not a true alpha blend of all the textures on the model. There are layers underneath that show through which I don’t want to see. I want to apply the fade-out AFTER all the underneath textures have been masked by the ones on top, as it would look with a non-alpha-blended shader, then made partially transparent after that.

Here is an image of the model at full alpha using my modified shader:

Here is what happens at partial transparency:

I’m trying to avoid seeing all the stuff “underneath”, if that makes sense.

Here is my modified shader so far:

Shader "Custom/ShadierFade"
{
	Properties
	{
	  _Opacity ("Opacity", Range(0.0, 1.0)) = 1.0
	  _MainTex ("Base and Alpha (RGBA)", 2D) = "white" {}
	  _Specular ("Specular", 2D) = "black" {}
	  _SpecAmount ("Specular Amount", Range(0.0, 1.0)) = 0.0
	  _BumpMap ("BumpMap", 2D) = "bump" {}
	  _Emission ("Emission", 2D) = "black" {}
	  _Color ("Main Color", Color) = (1,1,1,1)
	  _RimColor ("Rim Color", Color) = (0.15, 0.15, 0.15, 0.0)
	  _RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0
	}

	SubShader 
	{
		Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" }

		LOD 200
		ZWrite On
		Cull Off
		//AlphaTest GEqual 0.9
		AlphaTest GEqual [_Opacity]
		Blend SrcAlpha OneMinusSrcAlpha

		CGPROGRAM
		#pragma surface surf SimpleBlinnPhong noforwardadd

		half4 LightingSimpleBlinnPhong (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) 
		{
			half3 h = normalize (lightDir + viewDir);
			half diff = max (0, dot (s.Normal, lightDir));

			float nh = max (0, dot (s.Normal, h));
			float spec = pow (nh, s.Specular * 128.0) * s.Gloss;

			half4 c;
			c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
			c.a = s.Alpha;
			return c;
		}

		sampler2D _MainTex;
		sampler2D _Specular;
		sampler2D _Gloss;
		sampler2D _BumpMap;
		sampler2D _Emission;
		float4 _Color;
		float4 _RimColor;
		float _RimPower;
		float _GlossAmount;
		float _SpecAmount;
		float _Opacity;

		struct Input 
		{
			float2 uv_MainTex;
			float2 uv_Specular;
			float2 uv_Gloss;
			float2 uv_BumpMap;
			float2 uv_Emission;
			float3 viewDir;
		};

		void surf (Input IN, inout SurfaceOutput o)
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
    
			o.Albedo = c.rgb * _Color.rgb;

			//o.Alpha = c.a;
			//clip(c.a - 0.9);
			o.Alpha = c.a * _Opacity;

			o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));
			o.Gloss = tex2D (_Specular, IN.uv_Specular).r;
			o.Specular = _SpecAmount;
			half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
			o.Emission = clamp(tex2D (_Emission, IN.uv_Emission).rgb + _RimColor.rgb * min(pow(rim, _RimPower), 0.9), half3 (0, 0, 0), half3 (1, 1, 1));
		}

		ENDCG

		// fringe alpha pass
		ZWrite Off
		ZTest Less
		//AlphaTest Less 0.9
		AlphaTest Less [_Opacity]
		Blend SrcAlpha OneMinusSrcAlpha

		CGPROGRAM
		#pragma surface surf SimpleBlinnPhong noforwardadd
  
		half4 LightingSimpleBlinnPhong (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) 
		{
			half3 h = normalize (lightDir + viewDir);
			half diff = max (0, dot (s.Normal, lightDir));
  
			float nh = max (0, dot (s.Normal, h));
			float spec = pow (nh, s.Specular * 128.0) * s.Gloss;

			half4 c;
			c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
			c.a = s.Alpha;
			return c;
		}
  
		sampler2D _MainTex;
		sampler2D _Specular;
		sampler2D _Gloss;
		sampler2D _BumpMap;
		sampler2D _Emission;
		float4 _Color;
		float4 _RimColor;
		float _RimPower;
		float _GlossAmount;
		float _SpecAmount;
		float _Opacity;
  
		struct Input
		{
			float2 uv_MainTex;
			float2 uv_Specular;
			float2 uv_Gloss;
			float2 uv_BumpMap;
			float2 uv_Emission;
			float3 viewDir;
		};
  
		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			o.Albedo = c.rgb * _Color.rgb;

			//o.Alpha = c.a;
			//o.Alpha = c.a * _Color.a;
			o.Alpha = c.a * _Opacity;

			o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));
			o.Gloss = tex2D (_Specular, IN.uv_Specular).r;
			o.Specular = _SpecAmount;
			half rim = 0.0; // 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
			o.Emission = clamp(tex2D (_Emission, IN.uv_Emission).rgb + _RimColor.rgb * min(pow(rim, _RimPower), 0.9), half3 (0, 0, 0), half3 (1, 1, 1));
		}

		ENDCG


	}

	FallBack "Bumped Diffuse"
}

This post should help solve your problem.

1 Like

Thanks Daniel. The shader technique in your link was interesting, and it did help with the “back to front” problem, but now it seems that I’ve traded that problem for a new problem, which is that the “cutout” areas are now cut out of the entire model. Here’s a screenshot with thew new Colormap 0 pass implemented:

And here is my implementation, in case I got it wrong:

Shader "Custom/ShadierFade"
{
	Properties
	{
	  _Opacity ("Opacity", Range(0.0, 1.0)) = 1.0
	  _MainTex ("Base and Alpha (RGBA)", 2D) = "white" {}
	  _Specular ("Specular", 2D) = "black" {}
	  _SpecAmount ("Specular Amount", Range(0.0, 1.0)) = 0.0
	  _BumpMap ("BumpMap", 2D) = "bump" {}
	  _Emission ("Emission", 2D) = "black" {}
	  _Color ("Main Color", Color) = (1,1,1,1)
	  _RimColor ("Rim Color", Color) = (0.15, 0.15, 0.15, 0.0)
	  _RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0
	}

	SubShader 
	{
		Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" }

		// Render into depth buffer only
		Pass
		{
			ColorMask 0
		}

		LOD 200
		ZWrite On
		Cull Off
		//AlphaTest GEqual 0.9
		AlphaTest GEqual [_Opacity]
		Blend SrcAlpha OneMinusSrcAlpha

		CGPROGRAM
		#pragma surface surf SimpleBlinnPhong noforwardadd

		half4 LightingSimpleBlinnPhong (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) 
		{
			half3 h = normalize (lightDir + viewDir);
			half diff = max (0, dot (s.Normal, lightDir));

			float nh = max (0, dot (s.Normal, h));
			float spec = pow (nh, s.Specular * 128.0) * s.Gloss;

			half4 c;
			c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
			c.a = s.Alpha;
			return c;
		}

		sampler2D _MainTex;
		sampler2D _Specular;
		sampler2D _Gloss;
		sampler2D _BumpMap;
		sampler2D _Emission;
		float4 _Color;
		float4 _RimColor;
		float _RimPower;
		float _GlossAmount;
		float _SpecAmount;
		float _Opacity;

		struct Input 
		{
			float2 uv_MainTex;
			float2 uv_Specular;
			float2 uv_Gloss;
			float2 uv_BumpMap;
			float2 uv_Emission;
			float3 viewDir;
		};

		void surf (Input IN, inout SurfaceOutput o)
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
    
			o.Albedo = c.rgb * _Color.rgb;

			//o.Alpha = c.a;
			//clip(c.a - 0.9);
			o.Alpha = c.a * _Opacity;

			o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));
			o.Gloss = tex2D (_Specular, IN.uv_Specular).r;
			o.Specular = _SpecAmount;
			half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
			o.Emission = clamp(tex2D (_Emission, IN.uv_Emission).rgb + _RimColor.rgb * min(pow(rim, _RimPower), 0.9), half3 (0, 0, 0), half3 (1, 1, 1));
		}

		ENDCG

		// fringe alpha pass
		ZWrite Off
		ZTest Less
		//AlphaTest Less 0.9
		AlphaTest Less [_Opacity]
		Blend SrcAlpha OneMinusSrcAlpha

		CGPROGRAM
		#pragma surface surf SimpleBlinnPhong noforwardadd
  
		half4 LightingSimpleBlinnPhong (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) 
		{
			half3 h = normalize (lightDir + viewDir);
			half diff = max (0, dot (s.Normal, lightDir));
  
			float nh = max (0, dot (s.Normal, h));
			float spec = pow (nh, s.Specular * 128.0) * s.Gloss;

			half4 c;
			c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
			c.a = s.Alpha;
			return c;
		}
  
		sampler2D _MainTex;
		sampler2D _Specular;
		sampler2D _Gloss;
		sampler2D _BumpMap;
		sampler2D _Emission;
		float4 _Color;
		float4 _RimColor;
		float _RimPower;
		float _GlossAmount;
		float _SpecAmount;
		float _Opacity;
  
		struct Input
		{
			float2 uv_MainTex;
			float2 uv_Specular;
			float2 uv_Gloss;
			float2 uv_BumpMap;
			float2 uv_Emission;
			float3 viewDir;
		};
  
		void surf (Input IN, inout SurfaceOutput o) 
		{
			half4 c = tex2D (_MainTex, IN.uv_MainTex);

			o.Albedo = c.rgb * _Color.rgb;

			//o.Alpha = c.a;
			//o.Alpha = c.a * _Color.a;
			o.Alpha = c.a * _Opacity;

			o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));
			o.Gloss = tex2D (_Specular, IN.uv_Specular).r;
			o.Specular = _SpecAmount;
			half rim = 0.0; // 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
			o.Emission = clamp(tex2D (_Emission, IN.uv_Emission).rgb + _RimColor.rgb * min(pow(rim, _RimPower), 0.9), half3 (0, 0, 0), half3 (1, 1, 1));
		}

		ENDCG


	}

	FallBack "Bumped Diffuse"
}

You can put the same AlphaTest directive in your depth priming pass, and I think that should ensure that the same fragments are discarded in both passes.

Thanks Daniel, but I’m not sure I’m understanding what you mean. Do you mean this?

		// Render into depth buffer only
		Pass
		{
			AlphaTest GEqual [_Opacity]
			ColorMask 0
		}

If so, then it doesn’t change anything about the render. Did I do it wrong?

I realized afterwards that there is a different AlphaTest directive on the final pass. However, placing that in the top pass makes the whole thing transparent again, defeating the point of the top pass.

		Pass
		{
			AlphaTest Less [_Opacity]
			ColorMask 0
		}

I’m now seeing the “back to front” problem again with this configuration.