- Home /
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:
Terrain with altitude band shader and one directional light at camera's position:
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!
Additionally, the view light has its Render $$anonymous$$ode set to Important, which should force it to be pixel-lit when using the Forward render path.
Answer by Xtro · Aug 01, 2013 at 04:36 PM
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.
Thanks, yes I'm targeting iOS and Android as well as desktop, so will need support. I'm still on 4.1.5 - will install 4.2 and give that a try, thanks.
If you get the correct behavior on deferred mode(On desktop player), I suggest you to stick with it. On mobile, it's decreased to forward automatically if the device doesn't support it. Yes, the terrain may look bad on mobile but there is a good reason for it. The mobile doesn't support it. I hope your terrain doesn't look awful on mobile :(
Yes, tried it, and it made no difference on desktop or mobile. Still looking for a solution to this.
Your answer
