# Discontinuous Normals on a Normalized Cube Mesh with Displacement Texture

In my project, I have created a normalized cube using six plane meshes. To improve its visual quality, I added a displacement texture and calculated the normals for that texture to ensure proper lighting. However, I’m facing an issue where the resulting normals appear discontinuous at the edges, revealing the underlying cube structure rather than achieving a smooth, sphere-like shape. you can see here: example

function vertexShader() {
return 
varying vec2 vUv;
uniform sampler2D u_tex;
uniform vec3 wTl;
uniform vec3 ps;

//normalize each plane
vec3 planeToSphere(vec3 p, vec3 localCenter){
return 100.0*normalize(p-localCenter) + localCenter;
}

varying vec3 v3n;
void main() {
vUv = uv;
vec3 newPosition   =  planeToSphere(position,wTl) ;
vec3 worldp = ( modelMatrix * vec4(newPosition,1.0)).xyz;
float n = texture2D(u_tex, vUv).x;
//newPosition = newPosition + 1.0*n*normalize(position-wTl); // this add displacement
gl_Position = projectionMatrix  * modelViewMatrix * vec4( newPosition, 1.0 ) ;
}

}

return 
varying vec2 vUv;
uniform sampler2D u_tex;
varying vec3 v3n;

vec4 displacementMapToNormalMap(sampler2D displacementMap, vec2 vUv){
float scale    = 0.9;   // Adjust this to control the amount of displacement
float epsilon  = 0.01;  // Small value for calculating gradients
float strength = 1.;
float center = texture2D(displacementMap, vUv).r; // Sample displacement map
float dx = texture2D(displacementMap, vUv + vec2(epsilon, 0.0)).r - center;  // Calculate gradients in the X  directions
float dy = texture2D(displacementMap, vUv + vec2(0.0, epsilon)).r - center;  // Calculate gradients in the Y directions
vec3 normalMap = normalize(vec3(dx * scale, dy * scale, 1.0));               // Calculate the normal vector
normalMap *= strength;                                                       // Apply strength to the normal vector
return vec4(normalMap * 0.5 + 0.5, 1.0);                                     // Output the resulting normal as a color
}

void main() {
vec3 lightDirection = normalize(vec3(0.,1.,1.));
vec4 normalMapColor = displacementMapToNormalMap(u_tex, vUv);          // Get the normal map color
float grayscale = dot(normalMapColor.rgb, vec3(0.299, 0.587, 0.114));  // Convert the normal map color to grayscale
vec4 grayColor = vec4(grayscale, grayscale, grayscale, 1.0);
float lightIntensity = dot(normalMapColor.rgb, lightDirection);       // Calculate the lighting contribution
vec4 finalColor = grayColor * lightIntensity;                         // Apply the lighting to the grayscale color
gl_FragColor = finalColor;                                            // Output the resulting color
}

}


I have been working on this problem for weeks, but I have yet to make significant progress. During my research, I came across a relevant Stack Overflow question titled “How can I eliminate normal map seams on Cube Mapped Three.js BoxBufferGeometry?” you can see here.

One of the responses suggests not using the tangent frame provided by Three.js and instead generating tangents using the vertex data to align them at the seam. However, this is still wrong because The bi/tangents can not be continuous over the sphere (the Hairy Ball theorem). All the research I have done has led me to a dead end. I have seen a few posts on the web related to this same question but never an actual solution. Someone out there has to know how to tackle this problem.

I would greatly appreciate any guidance or suggestions on how to resolve the issue of discontinuous normals and achieve a smooth appearance for the normalized cube with a displacement texture. Thank you in advance for your help.

• "not using the tangent frame provided by Three.js and instead generating tangents using the vertex data to align them at the seam" — correct. Your tangents need to reflect the distortion you've applied to the planes. "However, this is still wrong because The bi/tangents can not be continuous over the sphere" — incorrect. The tangent basis does not need to be continuous over the whole sphere, only in the interior of each plane. It will have discontinuities at the plane seams, just like any other texturing seam. Can you share the texture you're using for the sake of building a test/demo? Commented Jul 15, 2023 at 12:34
• @DMGregory here is the demo codepen.io/miguel007/pen/QWJQpvw?editors=0010 The links to the textures are in the HTML section. Commented Jul 15, 2023 at 15:06
• Your function for calculating a normal requires reading adjacent heights from the texture. How will it know what the heights are on the far side of the seam where the sphere is reading from a different texture entirely? If you read past the edge of the texture, you'll get the last height on this side of the seam by default, which will not accurately match the texture you're trying to line up with. Commented Jul 15, 2023 at 15:15
• @DMGregory Hmm, So If my normal function is reading past the textures and causing issues at the seams what approach should I take? Commented Jul 15, 2023 at 16:01
• You could bake a normal map in a pre-process so you don't have to infer normals from height or read past the edges. You might also be able to set up your 6 separate textures as a single cubemap, though your sampling code would have to change. Commented Jul 15, 2023 at 18:46

so, the solution was to use a CubeTexture to make the normals continues.

solution

compute the normals like so.

   vec4 displacemntNormalCubeMap(samplerCube u_displacementMap, vec3 vUV){
float scale    = 4.9;   // Adjust this to control the amount of displacement
float epsilon  = 0.1;  // Small value for calculating gradients
float strength = 1.;
float center = textureCube(u_displacementMap, vUV).r; // Sample displacement map
float dx = textureCube(u_displacementMap, vUV + vec3(epsilon, 0.0, 0.0)).r - center;  // Calculate gradients in the X  directions
float dy = textureCube(u_displacementMap, vUV + vec3(0.0, 0.0, epsilon)).r - center;  // Calculate gradients in the Y directions
vec3 normalMap = normalize(vec3(dx * scale, dy * scale, 1.0));               // Calculate the normal vector
normalMap *= strength;                                                       // Apply strength to the normal vector
return vec4(normalMap * 0.5 + 0.5, 1.0);                                     // Output the resulting normal as a color
}


• These epsilons are correct only for one side of the cube. You'll get distorted normals away from this face using this code. Commented Jul 16, 2023 at 23:36
• @DMGregory hmm, what approach i should take to solve that? Commented Jul 16, 2023 at 23:51