Mixed vert/frag/surf shader error: "redefinition _MainTex_ST"

I’m trying to write a custom shader which uses multiple passes, first an arbitrary number of vert/frag passes followed by a GrabPass{} followed, finally, by a surf/surface shader pass. However, I need to use _MainTex_ST at least once in the vert/frag pass then also in the surf pass. Somehow this is causing an issue though where _MainTex_ST becomes either undeclared when it’s used in the vert pass or I get a redefinition error in the compiler.

Here is a small sample shader I made which recreates my issue. I added comments around the definition of _MainTex_ST explaining how to get either compiler error:

Shader "Custom/RedefinitionProblem"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        CGINCLUDE

        #pragma target 3.0
        #include "UnityCG.cginc"

        struct v2f_custom
        {
            float2 uv : TEXCOORD0;
            float4 grabPos : TEXCOORD1;
            float4 pos : SV_POSITION;
        };

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

        sampler2D _MainTex;
        float4 _MainTex_ST; // Commenting this out = "undeclared identifier '_MainTex_ST'" but leaving it in...
        // ...causes "redefinition of '_MainTex_ST'"

        v2f_custom vertBlur(appdata_custom v) {
            v2f_custom o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.grabPos = ComputeGrabScreenPos(o.pos);
            o.uv = TRANSFORM_TEX(v.uv, _MainTex); // <-- This is where "undeclared identifier '_MainTex_ST'" happens
            return o;
        }

        ENDCG

        Tags{ "Queue" = "Transparent" }

        GrabPass{}

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
           
            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
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }

        GrabPass{}

        // === Start of surface shader pass ===
        Cull Back ZWrite On
        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
            "IgnoreProjector" = "True"
        }
       
        CGPROGRAM

        // Physically based Standard lighting model
        #pragma surface surf Standard vertex:vert

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        struct Input {
            float2 uv_MainTex;
            float4 screenPos;
            float3 viewDir;
            float3 worldPos;
            float3 worldNormal; INTERNAL_DATA
            float3 worldRefl;
            float eyeDepth;
        };   

        void vert(inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            COMPUTE_EYEDEPTH(o.eyeDepth);
        }   

        void surf(Input i, inout SurfaceOutputStandard o) {
            o.Albedo = tex2D(_MainTex, i.uv_MainTex);
        }

        ENDCG
    }
}

How can I fix this?

Hi!
Perhaps CGINCLUDE on line 9 is what’s causing it?
As far as I understand it, It’s copying everything between CGINCLUDE and the next ENDCG multiple times (once into each CGPROGRAM).

Is there a way to stop it from copying into just that surface pass? Otherwise I’ll have to manually copy everything in it to all the vert passes (of which I have 8 of)

Just remove the float4 _MainTex_ST from the CGINCLUDE and put it in the CGPROGRAM blocks.

The nature of the CGINCLUDE is it gets added first before any other code. The fallout of that is you can’t modify the contents of the CGINCLUDE based on anything defined in the later CGPROGRAM blocks. An example of this would be if you could #if out your float4 _MainTex_ST based on some #define in the later CGPROGRAM blocks. Surface shaders add a ton of these so you can do custom code per-pass within the surf function, but since those will run the #define after the CGINCLUDE has already been added it can’t be used for this.

I very rarely use CGINCLUDE these days for this reason and instead put everything into .cginc files as then you have more control over when the code is inserted.

1 Like

It took a few extra changes but I finally got it to compile without a problem:

Shader "Custom/RedefinitionProblem"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags{ "Queue" = "Transparent" }

        GrabPass{}

        Pass
        {
            CGPROGRAM
            #pragma target 3.0

            #include "UnityCG.cginc"

            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct v2f_custom
            {
                float2 uv : TEXCOORD0;
                float4 grabPos : TEXCOORD1;
                float4 pos : SV_POSITION;
            };

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

            v2f_custom vertBlur(appdata_custom v) {
                v2f_custom o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.grabPos = ComputeGrabScreenPos(o.pos);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex); // <-- This is where "undeclared identifier '_MainTex_ST'" happens
                return o;
            }

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
          
            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
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }

        GrabPass{}

        // === Start of surface shader pass ===
        Cull Back ZWrite On
        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
            "IgnoreProjector" = "True"
        }
      
        CGPROGRAM

        #pragma target 3.0

        #include "UnityCG.cginc"

        sampler2D _MainTex;

        // Physically based Standard lighting model
        #pragma surface surf Standard vertex:vert

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        struct Input {
            float2 uv_MainTex;
            float4 screenPos;
            float3 viewDir;
            float3 worldPos;
            float3 worldNormal; INTERNAL_DATA
            float3 worldRefl;
            float eyeDepth;
        };  

        void vert(inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            COMPUTE_EYEDEPTH(o.eyeDepth);
        }  

        void surf(Input i, inout SurfaceOutputStandard o) {
            o.Albedo = tex2D(_MainTex, i.uv_MainTex);
        }

        ENDCG
    }
}

I think I’ll go with .cginc files from now on like you suggested.

Thanks!

Hi good night maybe is a little late but i think you can resolve your problem putting o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); instead of
o.uv = TRANSFORM_TEX(v.uv, _MainTex); im new aswell but i tried to rewrite a shadder that im writting using this little change and it worked for me ¡, i hope this information could be helpufully for you and by the way sorry for my bad english.

Just encountered this issue for a second time while working from a new surface shader. After lots of wasted time I figured it out for good so I thought I’d share since this post doesn’t appear to have a clear solution.

First off, here is an example of the bug recreated with a freshly generated Standard Surface Shader in Unity 2021.3.15f1

Shader "Custom/SurfaceShaderTest"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        fixed4 _MainTex_ST;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

NOTE: Yes I realize I’m not actually using the value of _MainTex_ST in this shader, this is just for the purpose of illustration.

Upon compilation I get the following error:

Shader error in ‘Custom/SurfaceShaderTest’: redefinition of ‘_MainTex_ST’ at line 64 (on d3d11)
Compiling Subshader: 0, Pass: FORWARD, Fragment program with POINT
Platform defines: SHADER_API_DESKTOP UNITY_ENABLE_DETAIL_NORMALMAP UNITY_ENABLE_REFLECTION_BUFFERS UNITY_LIGHTMAP_FULL_HDR UNITY_LIGHT_PROBE_PROXY_VOLUME UNITY_PASS_FORWARDADD UNITY_PBS_USE_BRDF1 UNITY_SPECCUBE_BLENDING UNITY_SPECCUBE_BOX_PROJECTION UNITY_USE_DITHER_MASK_FOR_ALPHABLENDED_SHADOWS
Disabled keywords: DIRECTIONAL DIRECTIONAL_COOKIE FOG_EXP FOG_EXP2 FOG_LINEAR LIGHTMAP_SHADOW_MIXING POINT_COOKIE SHADER_API_GLES30 SHADOWS_CUBE SHADOWS_DEPTH SHADOWS_SCREEN SHADOWS_SHADOWMASK SHADOWS_SOFT SPOT UNITY_ASTC_NORMALMAP_ENCODING UNITY_COLORSPACE_GAMMA UNITY_ENABLE_NATIVE_SHADOW_LOOKUPS UNITY_FRAMEBUFFER_FETCH_AVAILABLE UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS UNITY_HARDWARE_TIER1 UNITY_HARDWARE_TIER2 UNITY_HARDWARE_TIER3 UNITY_LIGHTMAP_DLDR_ENCODING UNITY_LIGHTMAP_RGBM_ENCODING UNITY_METAL_SHADOWS_USE_POINT_FILTERING UNITY_NO_DXT5nm UNITY_NO_FULL_STANDARD_SHADER UNITY_NO_SCREENSPACE_SHADOWS UNITY_PBS_USE_BRDF2 UNITY_PBS_USE_BRDF3 UNITY_PRETRANSFORM_TO_DISPLAY_ORIENTATION UNITY_UNIFIED_SHADER_PRECISION_MODEL UNITY_VIRTUAL_TEXTURING

Now to fix this, I’d change the above shader to the following:

Shader "Custom/SurfaceShaderTest"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_TexCoords;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        fixed4 _MainTex_ST;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_TexCoords) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

The solution is simply changing the name of the uv_MainTex property in the “Input” structure to “uv_TexCoords” and updating it’s references in the surf function as well.

The reason this appears to be happening can be observed upon clicking “show generated code” in the Inspector tab with the broken shader selected in the Project tab. I won’t paste the whole output here because it’s huge, but the important part is the redefinition of float4 _MainTex_ST that Unity adds in just after the v2f_surf structure definition:

// high-precision fragment shader registers:
#ifndef UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS
struct v2f_surf {
  UNITY_POSITION(pos);
  float2 pack0 : TEXCOORD0; // _MainTex
  float3 worldNormal : TEXCOORD1;
  float3 worldPos : TEXCOORD2;
  float4 lmap : TEXCOORD3;
  UNITY_FOG_COORDS(4)
  UNITY_SHADOW_COORDS(5)
  #ifdef DIRLIGHTMAP_COMBINED
  float3 tSpace0 : TEXCOORD6;
  float3 tSpace1 : TEXCOORD7;
  float3 tSpace2 : TEXCOORD8;
  #endif
  UNITY_VERTEX_INPUT_INSTANCE_ID
  UNITY_VERTEX_OUTPUT_STEREO
};
#endif
#endif
float4 _MainTex_ST;

So hopefully that helps someone, or at least me next time I encounter this issue and have long since forgotten how I fixed it… and hey, maybe Unity will read this and change their code to prefix their system generated variables with something unlikely to clash with game developers naming conventions, like an alphanumeric hash prefix or something.

The reason for the error is that it’s being defined by the surface shader.

Using float2 uv_MainTex; in the Input struct is telling it to apply _MainTex_ST to the mesh’s first uv set. So it also auto defines the uniform. The system is working as intended. If you don’t want it applied, don’t name it that.

The bit that doesn’t quite sit right with me is, even though I have:

        struct Input
        {
            float2 uv_LogoTex;
            float3 viewDir;
            float3 worldNormal;
        };

When I compile the shader, I get “undeclared identifier ‘_LogoTex_ST’”.

I don’t mind surface shaders defining variables for me based on the names I use in the struct, but it would be nice if it would at least do so consistently, or at the very least, in an inconsistent way which I can somehow predict.

This sort of nonsense is why I usually avoid surface shaders entirely.

I should add, this time around, the workaround of avoiding the name didn’t work either - I just got black UVs. I had to add custom struct fields and populate them from my own custom vertex shader to get any value other than zero in there. I got it working, but I think it would have been faster to do it as a fragment shader still.