Terrain custom shader not lit per pixel?

When applying a custom shader to a Terrain object, the terrain always appears to be vertex-lit, even though the shader is written to be per-pixel.

The original (textured) terrain:

alt text

Terrain with altitude band shader and one directional light at camera’s position:
alt text

This terrain has a pixel error setting of 4, as you can see, the quality is pretty poor with it ‘vertex lit’ like this.

Shader code is below. It looks like the normal values for the terrain are only coming out per-vertex, perhaps I am doing something wrong?

Shader "Custom/AltitudeShader" {

	Properties {
		_BaseColour ("Diffuse Material Colour", Color) = (1, 1, 1, 1)
		_Colour1 ("Colour 1", Color) = (0, 1, 0, 1)
		_Colour2 ("Colour 2", Color) = (1, 1, 0, 1)
		_Colour3 ("Colour 3", Color) = (1, .5, 0, 1)
		_Colour4 ("Colour 4", Color) = (1, 0, 0, 1)
		
		_Limit1 ("Limit 1", float) = 2000.0
		_Limit2 ("Limit 2", float) = 2500.0
		_Limit3 ("Limit 3", float) = 3000.0

	}
		
	SubShader {

		Pass {
			Tags { "LightMode" = "ForwardBase" } 
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			struct vertex_input {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct vertex_output {
				float4 pos : SV_POSITION;
				float4 world_pos : TEXCOORD0;
				float3 normal_dir : TEXCOORD1;
			};
			
			uniform float4 _LightColor0;
			float4 _BaseColour;
			float4 _Colour1;
			float4 _Colour2;
			float4 _Colour3;
			float4 _Colour4;
			float _Limit1;
			float _Limit2;
			float _Limit3;
			
			vertex_output vert(vertex_input i) {
				vertex_output o;
				o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
				o.world_pos = mul(_Object2World, i.vertex);
				o.normal_dir = i.normal;
				o.normal_dir = normalize(float3(mul(float4(i.normal, 0.0), _World2Object)));
				return o;
			}
			
			fixed4 frag(vertex_output i) : COLOR0 {
				
				//diffuse component
				float3 normal_dir = normalize(i.normal_dir);
				float3 view_dir = normalize(_WorldSpaceCameraPos - float3(i.world_pos));
				float3 light_dir;
				float attenuation;
				
				if(_WorldSpaceLightPos0.w == 0.0) {
					//directional light
					attenuation = 1.0;
					light_dir = normalize(float3(_WorldSpaceLightPos0));
				} else {
					//point or spot light
					float3 light_diff = float3(_WorldSpaceLightPos0 - i.world_pos);
					float3 dist = length(light_diff);
					attenuation = 1.0 / dist;
					light_dir = normalize(light_diff);
				}
				
				float3 ambient_light = float(UNITY_LIGHTMODEL_AMBIENT) * float3(_BaseColour);
				
				float3 diffuse_reflection = attenuation * _LightColor0.rgb * float3(_BaseColour) * max(0.0, dot(normal_dir, light_dir));
				
				//altitude bands
				float4 alt_col;
				if(i.world_pos.y < _Limit1) {
					alt_col = _Colour1;
				} else if(i.world_pos.y < _Limit2) {
					alt_col = _Colour2;
				} else if(i.world_pos.y < _Limit3) {
					alt_col = _Colour3;
				} else {
					alt_col = _Colour4;
				}
				alt_col *= (float4(diffuse_reflection, 1.0) * _BaseColour);
				return alt_col;
			}
			
			ENDCG
		}
	
    }	
}

Has anyone else come across this? Is it something inherent in shading Terrain objects, or is there a problem with my shader code? Any help greatly appreciated. Thanks!

I suggest you to try it on deferred render mode. I’m not a shader expert but I solved my shader problems by switching to the deferred render mode. I know it’s not supported on mobile platforms (if this is the case for you) but in Unity4.2 they started to support mobile platforms for deferred mode.