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%.