Shader changes during runtime works in Editor, but not on Android Device

I have a shader that I change during runtime via UI buttons. It changes the shader to either have a vertical or radial gradient, or activates a texture instead. It works fine in the editor, but doesn’t work at all when I run it on android. Will it even work on android? I have no idea what the problem could be, so any help is much appreciated!

Here’s the code:

Properties
    {
        _Color1("Color 1", Color) = (1.0, 0.5, 0.5, 1.0)
        _Color2("Color 2", Color) = (0.5, 0.5, 1.0, 1.0)
        _Scale("Scale", Range(0.1,3)) = 0
        [Toggle(VERTICAL_GRADIENT)] _VerticalGradient("Enable Vertical", Float) = 0.0
        [Toggle(TEXTURE_ON)] _TextureOn("Enable Texture", Float) = 0.0
        _MainTex("MainTexture", 2D) = "white" {}
    }

    SubShader
    {
        Cull Off
        ZWrite Off

        Tags
        {
            "Queue" = "Geometry"
            "PreviewType" = "Skybox"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #pragma shader_feature TEXTURE_ON
            #pragma shader_feature VERTICAL_GRADIENT

            float4 _Color1;
            float4 _Color2;
            float  _Scale;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct a2v
            {
                float4 vertex : POSITION;
            };

            struct v2f //interpolators
            {
                float4 vertex   : SV_POSITION;
                float4 position : TEXCOORD0;
            };

            struct f2g
            {
                float4 color : SV_TARGET;
            };

            void Vert(a2v i, out v2f o)
            {
                o.vertex = o.position = UnityObjectToClipPos(i.vertex);
            }

            float InverseLerp(float a, float b, float v)
            {
                return (v - a) / (b - a);
            }

            #ifdef TEXTURE_ON   
            void Frag(v2f i, out f2g o) //controls texture
            {
                float c = tex2D(_MainTex, (i.position.xy / i.position.w) * _MainTex_ST * _Scale);
                o.color = lerp(_Color1, _Color2, c);
            }
            #else
            void Frag(v2f i, out f2g o) //controls gradients
            {
            #ifdef VERTICAL_GRADIENT   
                float t = saturate(InverseLerp(_Scale, _Scale-3.5, i.position.y));
                o.color = lerp(_Color2, _Color1, t);
            #else
                o.color = lerp(_Color1, _Color2, saturate(length(i.position.xy / i.position.w) * _Scale));
            #endif
            }
            #endif
            ENDCG
        } // Pass

You want #pragma multi_compile for keywords you want to enable / disable at runtime. If you use shader_feature the build will strip any shader variants that aren’t referenced by materials used in the build, where as multi_compile will automatically include all variants.

I’d suggest using this one line instead of the two you have:

#pragma multi_compile _ TEXTURE_ON VERTICAL_GRADIENT

If you switch the two shader_feature lines you have now to multi_compile you’ll have the weird problem that the toggles you have will stop working even in the editor. This is because shader_feature and multi_compile annoyingly work slightly differently and shader_feature always assumes you want a variant of the shader without the keyword(s) listed after it enabled, where as multi_compile you have to be explicit about that by having one of the listed “keywords” be only one or more underscores. But if you keep it as two lines you’re asking Unity to create 4 variants of the shader when you really only want 3: no keywords, only VERTICAL_GRADIENT, or only TEXTURE_ON. You don’t ever need both TEXTURE_ON and VERTICAL_GRADIENT which having two multi_compile or shader_feature lines will allow for.

You also didn’t mention how you are trying to change which variant you use. Changing the material properties at runtime won’t do anything as they’re just dummy properties the material inspector is using to let it show the options. When you toggle the setting it’s calling mat.EnableKeyword() / mat.DisableKeyword() behind the scenes, which is what you’ll need to do as well.

5 Likes

bgolus, thank you so much for taking the time to write that detailed explanation and your help. I really appreciate it, you are just so helpful and awesome on these forums. That solved my issue and you helped me understand why. Thank you!!!

I don’t how you do it bgolus, you always stay ahead in rendering and for many years now… Thanks for this explanation.

1 Like