Too many texture interpolators

Hey forum,
I’ve been willing to target this one shader to 3.0. But it seems to be forcing me to target 4.0 constantly.
I need all those struct input values in order for the effect on the walls to work with:

  1. The uv’s that come with the model for mask texture.
  2. Vertex color,
  3. Local coord and local normal for triplanar texture mapping

Shader "Oot3D/effects/triplanar scroll"
{
    Properties
    {
        [NoScaleOffset] _MaskTexture ("Mask", 2D) = "white" {}
        [NoScaleOffset] _TriplanarTexture("Triplanar Texture", 2D) = "white" {}
        _MapScale("", Float) = 1
        _ScrollX ("Scroll X Speed", Float ) = 0.0
        _ScrollY ("Scroll Y Speed", Float ) = 0.0
        _ScrollZ ("Scroll Z Speed", Float ) = 0.0
        _ColorSpeed ("Color Change Speed", Float ) = 2.0
        _Intensity ("Intensity", Range(0,16)) = 2.0
        _Gamma ("Gamma", Range(1,2)) = 1.0
        _Contrast ("Contrast", Range(0,1)) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Blend SrcAlpha One
        CGPROGRAM
        #include "includes\output_adjust.cginc"
        #include "includes\hue_adjust.cginc"
        #pragma surface surf StandardSpecular vertex:vert
        #pragma target 3.0

        sampler2D _MaskTexture,_TriplanarTexture;
        half _MapScale;
        float _Intensity, _Contrast, _Gamma, _ColorSpeed;
        uniform float _ScrollX, _ScrollY, _ScrollZ;

        struct Input
        {
            float2 uv_MaskTexture : TEXCOORD0;
            float3 localCoord : POSITION;
            float3 localNormal : NORMAL;
            float4 vertColor : COLOR;
        };
        void vert(inout appdata_full v, out Input data)
        {
            UNITY_INITIALIZE_OUTPUT(Input, data);
            data.localCoord = v.vertex.xyz;
            data.localCoord.x += _ScrollX * _Time.y;
            data.localCoord.y += _ScrollY * _Time.y;
            data.localCoord.z += _ScrollZ * _Time.y;
            data.localNormal = v.normal.xyz;
        }
        void surf(Input IN, inout SurfaceOutputStandardSpecular o)
        {
            float3 bf = normalize(abs(IN.localNormal));
            bf /= dot(bf, (float3)1);

            float2 tx = IN.localCoord.yz * _MapScale;
            float2 ty = IN.localCoord.zx * _MapScale;
            float2 tz = IN.localCoord.xy * _MapScale;

            half4 cx = tex2D(_TriplanarTexture, tx) * bf.x;
            half4 cy = tex2D(_TriplanarTexture, ty) * bf.y;
            half4 cz = tex2D(_TriplanarTexture, tz) * bf.z;
            half4 color = (cx + cy + cz);
            float3 v = IN.vertColor;
            color.rgb = Contrast(color.rgb, _Contrast);
            color.rgb = Gamma(color.rgb, _Gamma);
            _Intensity *= .5 + (cos((_ColorSpeed * _Time.y)*32)*.5);
            v *= _Intensity;
            color.rgb *= v.rgb;
            o.Specular = float3(0,0,0);
            float3 AddColor = float3(1,0,0);
            AddColor = applyHue(AddColor,(_ColorSpeed * _Time.y) * 255);
            o.Emission = color.rgb * AddColor;

            o.Emission.rgb *= tex2D (_MaskTexture, IN.uv_MaskTexture);
            o.Alpha = color.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Anyone knows how I can cut off a part of the code without breaking the effect and allowing me to target 3.0?
Thanks in advance!

Just did a rewrite to fragment instead. Man, surface shaders absolutely suck.

Shader "Unlit/triplanar"
{
    Properties
    {
        [NoScaleOffset] _MaskTexture ("Mask", 2D) = "white" {}
        [NoScaleOffset] _TriplanarTexture("Triplanar Texture", 2D) = "white" {}
        _MapScale("", Float) = 1
        _ScrollX ("Scroll X Speed", Float ) = 0.0
        _ScrollY ("Scroll Y Speed", Float ) = 0.0
        _ScrollZ ("Scroll Z Speed", Float ) = 0.0
        _ColorSpeed ("Color Change Speed", Float ) = 2.0
        _Intensity ("Intensity", Range(0,16)) = 2.0
        _Gamma ("Gamma", Range(1,2)) = 1.0
        _Contrast ("Contrast", Range(0,1)) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Blend SrcAlpha One
        LOD 100

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

            #include "UnityCG.cginc"
            #include "includes\output_adjust.cginc"
            #include "includes\hue_adjust.cginc"

            sampler2D _MaskTexture,_TriplanarTexture;
            float4 _MaskTexture_ST;
            half _MapScale;
            float _Intensity, _Contrast, _Gamma, _ColorSpeed;
            uniform float _ScrollX, _ScrollY, _ScrollZ;

            struct appdata
            {
                float4 vertex : POSITION;
                fixed3 localCoord : TEXCOORD1;
                float2 uv : TEXCOORD0;
                fixed4 vertColor : COLOR;
                fixed3 localNormal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                fixed3 localCoord : TEXCOORD1;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                fixed4 vertColor : COLOR;
                fixed3 localNormal : NORMAL;
            };


            v2f vert (appdata v)
            {
                v2f o;

                o.localCoord = v.vertex.xyz;
                o.localCoord.x += _ScrollX * _Time.y;
                o.localCoord.y += _ScrollY * _Time.y;
                o.localCoord.z += _ScrollZ * _Time.y;
                o.localNormal = v.localNormal.xyz;

                o.vertColor = v.vertColor;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MaskTexture);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 bf = normalize(abs(i.localNormal));
                bf /= dot(bf, (float3)1);

                float2 tx = i.localCoord.yz * _MapScale;
                float2 ty = i.localCoord.zx * _MapScale;
                float2 tz = i.localCoord.xy * _MapScale;

                half4 cx = tex2D(_TriplanarTexture, tx) * bf.x;
                half4 cy = tex2D(_TriplanarTexture, ty) * bf.y;
                half4 cz = tex2D(_TriplanarTexture, tz) * bf.z;
                half4 color = (cx + cy + cz);

                fixed4 v = i.vertColor;
                color.rgb = Contrast(color.rgb, _Contrast);
                color.rgb = Gamma(color.rgb, _Gamma);
                _Intensity *= .5 + (cos((_ColorSpeed * _Time.y)*32)*.5);
                v *= _Intensity;
                color.rgb *= v.rgb;

                fixed3 AddColor = fixed3(1,0,0);
                AddColor = applyHue(AddColor,(_ColorSpeed * _Time.y) * 255);

                float MaskTex = tex2D(_MaskTexture, i.uv).r;
                half4 col = color;
                col.rgb *= AddColor;
                col *= MaskTex.r;
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

Surface Shaders are intended for shaders that interface with Unity’s lighting system. If you don’t want lighting, don’t use them. Switching to a vertex fragment shader for this was the correct thing to do even if you had gotten the above working.

And you could have gotten this to work as you did not need the local normal. Internally the shader is using the world normal, which you can transform back into the local normal with normalize(mul(worldNormal, (float3x3)unity_ObjectToWorld)). Also note that the semantics (the all caps stuff after the :) on the Input struct in a Surface Shader are all ignored, because that struct is not used to pass data between the vertex and the fragment shader stages. The one exception being the COLOR semantic, which the shader generation code just knows to look for, it’s still not used for the data interpolation. All values put into the Input struct are packed into a generated struct to minimize the number of texcoords used.

The problem is that by virtue of being a lit shader, there are a lot of interpolators needed just for the basic lighting system, specially for the Standard shading model. The other option you would have had would be to of changed to the Lambert shading model. Here’s the original Surface Shader tweaked slightly (and the contrast / hue stuff removed so it compiles locally for me) to work again.

Shader "Oot3D/effects/triplanar scroll"
{
    Properties
    {
        [NoScaleOffset] _MaskTexture ("Mask", 2D) = "white" {}
        [NoScaleOffset] _TriplanarTexture("Triplanar Texture", 2D) = "white" {}
        _MapScale("", Float) = 1
        _ScrollX ("Scroll X Speed", Float ) = 0.0
        _ScrollY ("Scroll Y Speed", Float ) = 0.0
        _ScrollZ ("Scroll Z Speed", Float ) = 0.0
        _ColorSpeed ("Color Change Speed", Float ) = 2.0
        _Intensity ("Intensity", Range(0,16)) = 2.0
        _Gamma ("Gamma", Range(1,2)) = 1.0
        _Contrast ("Contrast", Range(0,1)) = 1.0
    }
    SubShader
    {
        Tags { "Queue"="AlphaTest+50" "RenderType"="Transparent" }
        Blend SrcAlpha One
        ZWrite Off
        CGPROGRAM

        #pragma surface surf StandardSpecular vertex:vert
        #pragma target 3.0

        sampler2D _MaskTexture,_TriplanarTexture;
        half _MapScale;
        float _Intensity, _Contrast, _Gamma, _ColorSpeed;
        uniform float _ScrollX, _ScrollY, _ScrollZ;

        struct Input
        {
            float2 uv_MaskTexture : TEXCOORD0;
            float3 localCoord;
            float3 worldNormal;
            float4 vertColor : COLOR;
        };
        void vert(inout appdata_full v, out Input data)
        {
            UNITY_INITIALIZE_OUTPUT(Input, data);
            data.localCoord = v.vertex.xyz;
            data.localCoord.x += _ScrollX * _Time.y;
            data.localCoord.y += _ScrollY * _Time.y;
            data.localCoord.z += _ScrollZ * _Time.y;
        }
        void surf(Input IN, inout SurfaceOutputStandardSpecular o)
        {
            float3 localNormal = mul(IN.worldNormal, (float3x3)unity_ObjectToWorld);
            float3 bf = abs(localNormal);
            bf /= dot(bf, (float3)1);

            float2 tx = IN.localCoord.yz * _MapScale;
            float2 ty = IN.localCoord.zx * _MapScale;
            float2 tz = IN.localCoord.xy * _MapScale;

            half4 cx = tex2D(_TriplanarTexture, tx) * bf.x;
            half4 cy = tex2D(_TriplanarTexture, ty) * bf.y;
            half4 cz = tex2D(_TriplanarTexture, tz) * bf.z;
            half4 color = (cx + cy + cz);
            float3 v = IN.vertColor;
            _Intensity *= .5 + (cos((_ColorSpeed * _Time.y)*32)*.5);
            v *= _Intensity;
            color.rgb *= v.rgb;
            o.Specular = float3(0,0,0);
            o.Emission = color.rgb;

            o.Emission.rgb *= tex2D (_MaskTexture, IN.uv_MaskTexture);
            o.Alpha = color.a;
        }
        ENDCG
    }
}

Note I changed the render type, queue, disabled ZWrite, and removed the fallback. It looked like you’re trying to use this shader as some kind of overlay on top of an already existing material, so you want to make sure it renders after that otherwise there’s a chance it’ll prevent the “original” surface from rendering without the above changes.

But, again, using the vertex fragment shader is really the correct option. I would however make the same render type, queue, and ZWrite changes to that shader I made above.

I do have one question for you … why are you stuck on using #pragma target 3.0? Unity no longer supports any hardware that’s limited to that shader target! Any desktop hardware that modern versions of Unity can still run on will support at least #pragma target 4.0 anyway, as that’s the min spec for Unity from Unity 2017.3 on. For mobile if you want to support OpenGLES 2.0 platforms, you’d need to go down to #pragma target 2.0, at which point even my modified Surface Shader probably wouldn’t work! Otherwise OpenGLES 3.0 is #pragma target 3.5, which is basically the same as 4.0 but without geometry shaders. 3.5 will cover 91.5% of all mobile phones in use today.

1 Like

Thanks for the very detailed response! I’ve been greatly interested in shaders for the last 8 months.
I will look into the things you mentioned thoroughly to try and gain more understanding.

And yes, I noticed I should’ve been using fragment from the start. As most of the game uses it’s own basic vertex lit shading anyways. So I started my project with a folder full of surface shaders and now ended up having them all rewritten in a more simpler fragment.