# Normal Maps for Triplanar Mapped Objects

Posts: 1,389

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 2020

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.)

• Posts: 1,389

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 2020

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/

• Posts: 1,389

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;
}
``````