Need help with shader (unlit, solid color, alpha8 texture)

i’m trying to create an unlit shader that takes a color value (RGBA), and a grayscale texture as an alpha mask.
I also want it to be as cheap as possible since I want it working well on mobile platforms.
But, I have no experience writing shaders so I’ve come here for help.

This is what I’ve come up with so far:

Shader "Custom/UnlitColorAlphamask" {
    Properties {
        _Color ("Main Color (With Alpha)", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent"}
 
        ZWrite Off
 
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask RGB
 
        Pass {
            Lighting Off
            SetTexture[_MainTex] {
                constantColor [_Color]
                Combine texture * constant DOUBLE
            }
        }
    }
}

It works almost like I want it, but currently it uses an RGB texture rather than an Alpha8 texture (which to me seems like a waste of file space since I only need an alpha value)

If I change the compression to Alpha8 however, the color turns completely black (but with working alpha).
How can I modify this shader to take an Alpha8 compressed image as an input without that affecting it’s color?

Edit:
Since I have no experience writing shaders I’m also wondering how well this would work on mobile platforms

Alpha 8 only stores the color information in the alpha channel. But that fixed function shader code isn’t handling that case.

Also those FixedFunction shaders aren’t actually FixedFunction anymore but are converted to vert/frag under the hood and thus won’t give you a performance boost. In fact most mobile phones these days don’t have the hardware for fixed function shaders. Here is how it will be with in vert/frag form:

Shader "Unlit/BC4AlphaShader"
{
    Properties
    {
        _Color("Main Color (With Alpha)", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"}
        LOD 100

        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask RGB

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
       
                fixed4 col = tex2D(_MainTex, i.uv).rrrr; // R8/BC4 texture data is in RED channel, so fill all our color channels with .r
                //fixed4 col = tex2D(_MainTex, i.uv).aaaa; // Alpha8 color is stored in Alpha channel, use this line instead if going to use Alpha8 format.
                col.rgb *= _Color; //Multiply our RGB by our Tint color, changing our white into the color.
                UNITY_APPLY_FOG(i.fogCoord, col); //Fog will only be calculated if you have it enabled in game

                return col;
            }
            ENDCG
        }
    }
}

Alpha8 is a high quality uncompressed format though. I would recommend clicking on your texture, setting its “Texture Type” to “Single Channel”, and then select “Channel” as “Red” and have your mask texture be stored in your texture’s red channel. Then you can simply use the Compression quality settings on the “Default” tab at the bottom of the Texture Import Settings to let Unity decide the best single-channel format for a given platform and compression quality. “Crunch Compression” at a quality level of 50 tends to be pretty good for something like this.

Keep in mind, having just one texture channel to sample isn’t going to give you any significant performance advantage as the GPUs texture sampling units are built to sample all 4 channels at once, so it’s just a minuscule bit of data transfer time to those units you’re saving on. Mostly the savings you’ll get from this are GPU/CPU memory space savings, which are indeed quite important on mobile especially. And the time it takes to load these textures is lower too.

2 Likes

Thanks! I learned a lot from reading your post and the link about FixedFunction shaders.

The shader works pretty well, but I noticed that since the R channel is repeated for all of the RGBA channels, the RGB channels are darker in the areas where the alpha is lower. (i had this issue with my original shader too)

How can I make it so it uses the RGB value only from _Color, and not have _MainTex affect RGB at all?

This is what I could come up with, but I feel like I might be using an unnecessary amount of code since I’m first writing all the channels with rrrr from _MainTex and then replacing them with rgb from _Color

fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv).rrrr; //fill all color channels with the red channel
                col.rgba *= _Color; //Multiply RGBA by the color (added A to allow fading it out with A of _Color)
                col.rgb = _Color; //Set RGB back to the pure color value of _Color
                return col;
            }
1 Like

You may notice the edges being brighter in that case. But to do that, simply do:

fixed4 col = _Color;
col.a *= tex2D(_MainTex, i.uv).r;
return col;

This way you’re just outputting a pure color and letting the shader blend it with existing buffer color based on the alpha value, which will be your mask. (We multiply so that you can still have additional overall transparency control from the _Color swatch’s Alpha value as well if ever wanted, otherwise could just do col.a =)

1 Like

Thanks! This is exactly what I was looking for