# Normal Maps for Triplanar Mapped Objects

I'm seeing some lighting issues while using the TextureUniformTriplanar node in the Visual Shader. I have a NoiseTexture which I am using as both to calculate the planet colors and also to calculate the normal maps. If you look at the image below, you can see a light off in the distance (I've turned up the energy to make it more visible). The "shadowed" areas are not being lit correctly. If I go to the other side of the sphere, the lighting is correct.

Am I missing something here or is this a limitation of triplanar mapping? • edited July 18

Maybe the normal map direction is inverted. Godot uses normal maps with Y- direction (see this table for a comparison with other engines).

You'll need to use an external tool to swap the Y coordinate in the normal map. (There might be an import option for doing this in 4.0.)

• The normal map is generated by a built-in Godot plug-in so I would assume it's correct? The normal mapping is also correct on one side of the cube but it appears that the normals aren't adjusted properly for triplanar mapping.

• edited July 18

normal maps are dependent on coordinates iirc, for this particular case you might have a better result using either a derivative map or a bumpmap that you convert into normal data in the shader.

http://www.rorydriscoll.com/2012/01/11/derivative-maps/

• I ended abandoning the triplanar mapping (although I am glad I learned about it) and going back to this awesome tutorial and extended it by calculating the partial derivatives to calculate the tangent/normal vectors. I am quite happy with the result. For anyone that is interested, here's the shader

``````shader_type spatial;

// EXTERNAL PARAMS =============================================================

uniform int seed = 0;

// Colors for each type of terrain
uniform vec4 land_color: hint_color = vec4(0.17, 0.29, 0.16, 1.0);
uniform vec4 beach_color: hint_color = vec4(0.89, 0.80, 0.61, 1.0);
uniform vec4 ocean_color: hint_color = vec4(0.08, 0.25, 0.41, 1.0);
uniform vec4 mountain_color: hint_color = vec4(0.75, 0.75, 0.75, 1.0);

// Controls intensity of the normal mapping
uniform float bump_strength: hint_range(0.01, 1.0) = 0.5;

// Parameters for the noise model
// See https://docs.godotengine.org/en/stable/classes/class_opensimplexnoise.html
// for an explanation of the terms. This is my own implementation of it.
uniform float scale: hint_range(0.0, 2.0) = 1.06;
uniform float period: hint_range(0.01, 0.5) = 0.339;
uniform int octaves: hint_range(1, 10) = 5;
uniform float lacunarity: hint_range(0.1, 10.0) = 2.447;
uniform float persistence: hint_range(0.0, 1.0) = 0.493;
uniform float time = 0;

// INTERNAL PARAMS =============================================================

const float pi = 3.14159265;

// Magnitude of offset used for calculating tangent vectors
const float delta = 0.0001;

// Thresholds controlling transitions between each terrain color
const float th_beach_min = 0.5;
const float th_beach_max = 0.55;
const float th_land_min = 0.53;
const float th_land_max = 0.55;
const float th_mountain_min = 0.65;
const float th_mountain_max = 0.70;

// Author: Inigo Quilez
vec3 hash(vec3 p) {
p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6)));

return -1.0 + 2.0 * fract(sin(p + float(seed)) * 43758.5453123);
}

// Author: Inigo Quilez
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);

float n = mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );

// Normalize to [0.0, 1.0]
return 0.5*(n + 1.0);
}

// Converts (phi,theta) to (x,y,z) on a sphere
vec3 sph2cart(float phi, float theta) {
vec3 unit = vec3(0.0, 0.0, 0.0);
unit.x = sin(phi) * sin(theta);
unit.y = cos(theta) * -1.0;
unit.z = cos(phi) * sin(theta);
return normalize(unit);
}

void fragment() {
float theta = UV.y * pi;
float phi = UV.x * 3.14159 * 2.0;

// Get unit vectors which will be used to sample the 3D noise
// Perturb the unit vector in the theta/phi directions. Will be used
// to calculate the normals later
vec3 e = sph2cart(phi, theta);
vec3 e_dx = sph2cart(phi + delta, theta);
vec3 e_dy = sph2cart(phi, theta + delta);

// Accumulation variables for the noise samples. The noise is accumulated
// by adding the noise components for each octaves.
float n = 0.0;
float n_dx = 0.0;
float n_dy = 0.0;

float a = 1.0; // Amplitude for current octave
float max_amp = a; // Accumulate max amplitude so we can normalize after
float p = period;  // Period for current octave
for(int i = 0; i < octaves; i++) {
// Sample the 3D noise
n += a*noise(e/p + time);
n_dx += a*noise(e_dx/p + time);
n_dy += a*noise(e_dy/p + time);

// Amplitude decay for higher octaves
a *= persistence;
max_amp += a;
// Divide period by lacunarity
p = p / lacunarity;
}

// Scale noise to range [0.0, 1.0]
n /= float(max_amp) / scale;
n_dx /= float(max_amp) / scale;
n_dy /= float(max_amp) / scale;

// Perturb the unit vectors we calculated earlier by the noise
vec3 r = e*(1.0 + bump_strength*n);
vec3 r_dx = e_dx*(1.0 + bump_strength*n_dx);
vec3 r_dy = e_dy*(1.0 + bump_strength*n_dy);

// Build the tangent vectors and calculate normal
vec3 tangent_dx = normalize(r_dx - r);
vec3 tangent_dy = normalize(r_dy - r);

// Threshold values for transitioning from each terrain type
float th_beach = smoothstep(th_beach_min, th_beach_max, n);
float th_land = smoothstep(th_land_min, th_land_max, n);
float th_mountain = smoothstep(th_mountain_min, th_mountain_max, n);

// Mix between the flat surface normal (unit) and the normal associated with
// the terrain. This will cause the ocean areas to be shaded as a flat sphere.
vec3 normal_model = mix(e, cross(tangent_dx, tangent_dy), th_beach);

// Mix between the ocean color and the land color
vec3 color = mix(ocean_color.rgb, beach_color.rgb, th_beach);
color = mix(color.rgb, land_color.rgb, th_land);
color = mix(color.rgb, mountain_color.rgb, th_mountain);

// OUTPUTS

ALBEDO = color.rgb;

// Transform normals from model to view space
NORMAL = (INV_CAMERA_MATRIX*(WORLD_MATRIX*vec4(normal_model, 0.0))).xyz;
}
``````