Setting o.normal is making my shader do weird things

Very new to shaders so I apologize if I’m making a basic error in this.

I’m experimenting with making my own shader for procedural terrain meshes. I have it working right now as a diffuse-only shader that blends four different textures based on height and another based on slope (world normal).

I added code to do normal map blending as well, but weird stuff happens when I set o.normal. If I comment out that one line at the end and leave everything else as is, it functions perfectly.

The noted weirdnesses are 1) That certain diffuse textures are still there but others are “whited out” entirely; 2) that the diffuse blending breaks; and 3) the normal maps look weird anyway.

I appreciate any pointing in the right direction as I don’t really know how to debug this and can’t find any other shaders with similar errors. Thanks!

Shader follows:

Shader "Custom/Terrain" {
    Properties{
        //Diffuse maps
        _Elevation1Tex("Elevation 1 Diffuse", 2D) = "white" {}
        _Elevation2Tex("Elevation 2 Diffuse", 2D) = "white" {}
        _Elevation3Tex("Elevation 3 Diffuse", 2D) = "white" {}
        _Elevation4Tex("Elevation 4 Diffuse", 2D) = "white" {}
        _SlopeTex("Slope Diffuse", 2D) = "white" {}

        //Normal Maps
        _Elevation1Normal("Elevation 1 Normal", 2D) = "bump" {}
        _Elevation2Normal("Elevation 2 Normal", 2D) = "bump" {}
        _Elevation3Normal("Elevation 3 Normal", 2D) = "bump" {}
        _Elevation4Normal("Elevation 4 Normal", 2D) = "bump" {}
        _SlopeNormal("Slope Normal", 2D) = "bump" {}

        //For slope calculations.
        _VertDir("Vertical Direction", Vector) = (0.0, 1.0, 0.0)
        _SlopeIntensity("Slope Intensity", Range(0.1, 5.0)) = 2.0
        _MinSlope("Minimum Slope Attenuation", Range(0.0, 2.0)) = 0.1

        //Need one of each threshold + radius for (elevation maps)-1
        _Elev1Level("Elevation 1 Height Threshold", Range(0.0, 1.0)) = 0.1
        _Elev1Fade("Elevation 1 Attenuation Radius",Range(0.0,0.2)) = 0.05
        _Elev2Level("Elevation 2 Height Threshold", Range(0.0, 1.0)) = 0.2
        _TempTest("Elevation 2 Attenuation Radius",Range(0.0,0.2)) = 0.05
        _Elev3Level("Elevation 3 Height Threshold", Range(0.0, 1.0)) = 0.3
        _Elev3Fade("Elevation 3 Attenuation Radius",Range(0.0,0.2))= 0.05

    }
    SubShader{
        Tags { "RenderType" = "Opaque" }
        LOD 200
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        //#pragma surface surf Lambert
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 4.0

        sampler2D _Elevation1Tex;
        sampler2D _Elevation2Tex;
        sampler2D _Elevation3Tex;
        sampler2D _Elevation4Tex;
        sampler2D _SlopeTex;

        sampler2D _Elevation1Normal;
        sampler2D _Elevation2Normal;
        sampler2D _Elevation3Normal;
        sampler2D _Elevation4Normal;
        sampler2D _SlopeNormal;

        float4 _VertDir;
        float _SlopeIntensity;

        float _Elev1Level;
        float _Elev2Level;
        float _Elev3Level;
        float _Elev1Fade;
        float _TempTest;
        float _Elev3Fade;
        float _MinSlope; //Attenuate the slope fade-in factor from here (IE underwater to beaches we may not want cliff appearance)

        float minHeight;
        float maxHeight;

        struct Input {
            float3 worldPos;
            float3 worldNormal; INTERNAL_DATA //Need INTERNAL_DATA to pull normal vector since our shader writes to o.Normal
            float2 uv_Elevation1Tex;
            float2 uv_Elevation2Tex;
            float2 uv_Elevation3Tex;
            float2 uv_Elevation4Tex;
            float2 uv_SlopeTex;
            float2 uv_Elevation1Normal;
            float2 uv_Elevation2Normal;
            float2 uv_Elevation3Normal;
            float2 uv_Elevation4Normal;
            float2 uv_SlopeNormal;
        };

        float inverseLerp(float a, float b, float value) {
            return saturate((value - a) / (b - a));
        }

        void surf(Input IN, inout SurfaceOutputStandard o) {

            float heightPercent = inverseLerp(minHeight, maxHeight, IN.worldPos.y);

            fixed4 elev1Tex = tex2D(_Elevation1Tex, IN.uv_Elevation1Tex);
            fixed4 elev2Tex = tex2D(_Elevation2Tex, IN.uv_Elevation2Tex);
            fixed4 elev3Tex = tex2D(_Elevation3Tex, IN.uv_Elevation3Tex);
            fixed4 elev4Tex = tex2D(_Elevation4Tex, IN.uv_Elevation4Tex);
            fixed4 slopeTex = tex2D(_SlopeTex, IN.uv_SlopeTex);

            fixed4 elev1Normal = tex2D(_Elevation1Normal, IN.uv_Elevation1Normal);
            fixed4 elev2Normal = tex2D(_Elevation2Normal, IN.uv_Elevation2Normal);
            fixed4 elev3Normal = tex2D(_Elevation3Normal, IN.uv_Elevation3Normal);
            fixed4 elev4Normal = tex2D(_Elevation4Normal, IN.uv_Elevation4Normal);
            fixed4 slopeNormal = tex2D(_SlopeNormal, IN.uv_SlopeNormal);

            //Default to highest
            fixed4 diffuseOut = elev4Tex;
            fixed4 normalOut = elev4Normal;

            if (heightPercent < (_Elev1Level + _Elev1Fade)) {
                if (heightPercent < (_Elev1Level - _Elev1Fade)) {
                    diffuseOut = elev1Tex; //Pure texture 1
                    normalOut = elev1Normal;
                }
                else {
                    diffuseOut = lerp(elev1Tex, elev2Tex, inverseLerp(_Elev1Level - _Elev1Fade, _Elev1Level + _Elev1Fade, heightPercent));
                    normalOut = lerp(elev1Normal, elev2Normal, inverseLerp(_Elev1Level - _Elev1Fade, _Elev1Level + _Elev1Fade, heightPercent));
                }
            }
            else {

                if (heightPercent < (_Elev2Level + _TempTest)) {
                    if (heightPercent < (_Elev2Level - _TempTest)) {
                        diffuseOut = elev2Tex; //Pure texture 2
                        normalOut = elev2Normal;
                    }
                    else {
                        diffuseOut = lerp(elev2Tex, elev3Tex,inverseLerp(_Elev2Level - _TempTest, _Elev2Level + _TempTest, heightPercent));
                        normalOut = lerp(elev2Normal, elev3Normal, inverseLerp(_Elev1Level - _Elev1Fade, _Elev1Level + _Elev1Fade, heightPercent));
                    }
                }
                else {

                    if (heightPercent < (_Elev3Level + _Elev3Fade)) {
                        if (heightPercent < (_Elev3Level - _Elev3Fade)) {
                            diffuseOut = elev3Tex; //Pure texture 3
                            normalOut = elev3Normal;
                        }
                        else {
                            diffuseOut = lerp(elev3Tex, elev4Tex, inverseLerp(_Elev3Level - _Elev3Fade, _Elev3Level + _Elev3Fade, heightPercent));
                            normalOut = lerp(elev3Normal, elev4Normal, inverseLerp(_Elev1Level - _Elev1Fade, _Elev1Level + _Elev1Fade, heightPercent));
                        }
                    }
                    else {
                        diffuseOut = elev4Tex;
                        normalOut = elev4Normal;
                    }
                }
            }

            //Mix in the slope calculations
            float slope = 1-dot(normalize(IN.worldNormal), _VertDir.xyz);
            if (slope < 0) slope = 0;
            slope = slope * _SlopeIntensity;
            if (heightPercent < _MinSlope) {
                if (heightPercent < _MinSlope - 0.1) {         //For now hardcoded - 0.1 from start of attenuation, we lose slope blend effect
                    slope = 0;
                }
                else {
                    slope = slope * inverseLerp(_MinSlope - 0.1,_MinSlope,heightPercent);
                }
            }
            else {
            }

            diffuseOut = lerp(diffuseOut, slopeTex, slope);
            normalOut = lerp(normalOut, slopeNormal, slope);

            o.Albedo = diffuseOut.rgb;
            //o.Normal = UnpackNormal(normalOut);

            //Using normals is 'whiting out' some other textures and doesn't look very good,
            //so disable until we figure out the issue

        }
        ENDCG
    }
    FallBack "Diffuse"
}

You can do height blends without ifs, that’s going to kill your performance as is.

What do you get if you don’t use unpack normal and just do “o.Normal = normalOut*2 -1” ?

I’ll give it a shot. Are the ifs more expensive than the blending? I hadn’t even considered that. (Again, VERY new to shaders.) Will update when I give it a try - thank you.

Typically the GPU will render all branches then evaluate the correct result and display that one pixel, so you’d be rendering the same pixel more than once. Until you’re really sure what you’re doing its better to avoid branches.

Height blending textures for terrain is pretty common, you can find examples if you google around that will give you nice results.