Custom shader with Unity 4.6 UI Canvas Group Alpha problem

I’m currently using a custom shader on some UI elements in my game. I’m getting the results I want, however I’m using a Canvas Group component on the parent element whose Alpha value isn’t affecting my material as I’d expect.

Is there anything special I should be doing to to let this happen?

I’m going to go into detail on what @Braithy85 is talking about:

CanvasGroup dictates transparency using the alpha in the vertex color. What that means is that your shader needs to find and pass on the vertex color to the fragment shader, and the fragment shader should take the vertex color’s alpha into account when figuring out the color.

Here’s a shader I hacked together to fade all pixels in a texture to a certain tint (2D only, ignores lighting):

Shader "Sprites/Flash (Preserve Alpha)"
{
	Properties
	{
		[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
		_FlashAmount ("Flash Amount",Range(0.0,1.0)) = 0.0
		_Color ("Tint", Color) = (1,1,1,1)
	}

	SubShader
	{
		Tags
		{ 
			"Queue"="Transparent" 
			"IgnoreProjector"="True" 
			"RenderType"="Transparent" 
			"PreviewType"="Plane"
			"CanUseSpriteAtlas"="True"
		}
        Pass {

    		Cull Off
    		Lighting Off
    		ZWrite Off
            ZTest [unity_GUIZTestMode]
    		Fog { Mode Off }
    		Blend SrcAlpha OneMinusSrcAlpha

    		CGPROGRAM
    //        #pragma target 2.0              //Run this shader on at least Shader Model 2.0 hardware (e.g. Direct3D 9)
            #pragma vertex vert_img_cust    //The vertex shader is named 'vert'
            #pragma fragment frag           //The fragment shader is named 'frag'
            #include "UnityCG.cginc"        //Include Unity's predefined inputs and macros

    		sampler2D _MainTex;
    		fixed4 _Color;
    		float _FlashAmount,_SelfIllum;
    		
            struct v_in {
                float4 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
                float4 color : COLOR;
            };
            
    		struct Input
    		{
    			float2 uv_MainTex;
    			fixed4 color;
    		};
            
            struct v2f_img_vert_color {
                float4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                float4 diff : COLOR0;
            };
            
            v2f_img_vert_color vert_img_cust( v_in v )
            {
                v2f_img_vert_color o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.texcoord;
                o.diff =  v.color;
                return o;
            }

    		fixed4 frag(v2f_img_vert_color IN) : COLOR {
    			fixed4 c = tex2D(_MainTex, IN.uv);
                float3 rgbLerped = lerp(c.rgb, _Color, _FlashAmount);
                return (rgbLerped[0], rgbLerped[1], rgbLerped[2], IN.diff.a * c.a);
    		}
            ENDCG
        }
    }
}

Let’s break down what’s going on:

The most important part of the tags above for alpha is the

Blend SrcAlpha OneMinusSrcAlpha

which takes the alpha value and treats it like you’d expect (more on that here).

My vertex shader is exactly Unity’s vert_img shader, but also passes down the vertex color.

            struct v_in {
                float4 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
                float4 color : COLOR;
            };
            
            v2f_img_vert_color vert_img_cust( v_in v )
            {
                v2f_img_vert_color o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.texcoord;
                o.diff =  v.color;
                return o;
            }

To see what I’m talking about, google “UnityCG.cginc source location” to see the code. All this shader does is get the position of the object (not used in my fragment shader), the texture coordinates, and grabs the vertex color from the input vertex struct (by instructing the input struct to get it it using the : COLOR0 semantic). If you’re wondering what else you can grab from the vertex input, go here for a full specification (appdata specifies the app->vertex shader).

Finally, let’s talk about the fragment shader:

            struct v2f_img_vert_color {
                float4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                float4 diff : COLOR0;
            };

    		fixed4 frag(v2f_img_vert_color IN) : COLOR {
    			fixed4 c = tex2D(_MainTex, IN.uv);
                float3 rgbLerped = lerp(c.rgb, _Color, _FlashAmount);
                return (rgbLerped[0], rgbLerped[1], rgbLerped[2], IN.diff.a * c.a);
    		}

The fragment shader takes in a v2f_img_vert_color struct (defined above), and spits out the fragment color. The important thing to notice here is the last member of the fixed4 being returned:

return (rgbLerped[0], rgbLerped[1], rgbLerped[2], IN.diff.a * c.a);

This is just an RGBA value (what you’d expect a fragment shader to return). But there’s now a calculation for the alpha component!

IN.diff.a x c.a is the multiplication of the vertex’s alpha value with the texture pixel’s original alpha value. Why do this? This preserves transparency in the texture while also applying the vertex’s alpha to the color.

Think of it like this: You WANT the alpha in the original image to show through, but you also want to scale it relative to what the vertex’s alpha currently is. So if the original image has an alpha value for a pixel of 50% opacity, but your vertex is at 25% opacity, the final opacity of that pixel should be 50% * 25% = 12.5%.

After comparing it to their UI/Default shader I fixed it by sending the vertex colour through to the fragment shader. This alpha seems to be affected by the group canvas alpha

5 years later @mw_rgeorge answer is still gold and easy to understand, even for the ones who use shader graph like me.

FYI For anyone trying to apply @Braithy85’s solution to Shader Graph, there’s a Vertex Color node you can split alpha from and multiply/send to the alpha of your master node.