I’m projecting a texture on to a model by feeding the x and z world coordinates into the UV. This works fine for the texture but the normal map always receives light from the wrong direction.
I don’t know enough about normals to know why this is happening, if anybody could give me an idea I would be very thankful!
What do you use for those projections? I’ve been working with Slin’s projection shader from the Unity Wiki and added bumpmapping to it. I get an inverted normal texture on half of the faces; For example, on a cube, if the normals show correctly on the front face, they show inverted on the back face. I believe this is because the same data is projected on front and back faces by using a single projection. To solve that problem, I think you would need to do six projections and align them individually, at least for the bump map. In your screenshot above, it is likely that the underside of the terrain would have the normal texture correctly aligned.
Took some futzing, but you can achieve the desired results by flipping UV the negative values, as I show below. I’m calculating the sign in the vertex stage since otherwise I was compiling to 65 arithmetic instructions, just over the limitation for SM2.
Only using one set of textures, and I’ve also left out the scaling, but I didn’t want to clutter the example.
Shader "TriplanarBump" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap ("Bump (normal)", 2D) = "normal" {}
_blendBias ("Blend Tightness", range(0,0.5)) = 0.25
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert vertex:vert
#pragma target 2.0
sampler2D _MainTex;
sampler2D _BumpMap;
uniform float _blendBias;
struct Input {
float3 worldPos; // Handled automatically
float3 worldNormal; // Handled in Vert
half3 signs;
};
void vert (inout appdata_full v, out Input o) {
// Using float3x3 since only care about the rotation element
// (Correspondingly, float3x4 is TRS, float4x4 allows perspective)
o.worldNormal = mul((float3x3)_Object2World, SCALED_NORMAL);
o.signs = sign(o.worldNormal);
}
void surf (Input IN, inout SurfaceOutput o) {
half3 signs = IN.signs;
half2 uv1,uv2,uv3;
uv1 = IN.worldPos.yz; // X-projection
uv2 = IN.worldPos.xz; // Y-projeciton
uv3 = IN.worldPos.xy; // Z-projection
uv1 *= signs.x; uv2 *= signs.y; uv3 *= signs.z;
half3 blendWeights = abs(IN.worldNormal);
// Tighten the blending by parameter- Many textures look weird otherwise.
blendWeights -= _blendBias;
blendWeights = max(blendWeights,0);
//Without blend bias, it is <64 arithmatic instructions, and thus SM2
// In my lazy implementations, I normalize blendWeights
// However, to be more correct about it, they need to sum to one:
blendWeights *= 1/(blendWeights.x + blendWeights.y + blendWeights.z);
// Perform the color lookups for color values
half3 c1,c2,c3;
c1 = tex2D(_MainTex,uv1).xyz;
c2 = tex2D(_MainTex,uv2).xyz;
c3 = tex2D(_MainTex,uv3).xyz;
half3 bc =
(c1 * blendWeights.x) +
(c2 * blendWeights.y) +
(c3 * blendWeights.z);
half3 n1,n2,n3; // Packed normals use all texture channels
n1 = UnpackNormal(tex2D(_BumpMap,uv1)).xyz;
n2 = UnpackNormal(tex2D(_BumpMap,uv2)).xyz;
n3 = UnpackNormal(tex2D(_BumpMap,uv3)).xyz;
half3 bn =
(n1 * blendWeights.x) +
(n2 * blendWeights.y) +
(n3 * blendWeights.z);
o.Albedo = bc;
o.Normal = normalize(bn);
}
ENDCG
}
FallBack "Diffuse"
}