-1

In threejs, i'm working on a showerbase project using shader. One of my material got visible mineral veins on it and I noticed when it wrap around the border, the veins are not aligned.

I made a simple cube using the material to demonstrate the problem:

Cube

The final product can change in size and I can also cut geometry in it depending on user input but a simple cube illustrate well the problem. What technique should I use to achieve the desire result?

Here is the current code of the shader

 mat.onBeforeCompile = function( shader ) {
        if (mat.map) { 
            shader.uniforms.triplanarTexture = { value: mat.map }; 
            shader.uniforms.triplanarScale = mat.userData.triplanarScale;
            
            if (mat.normalMap) {
                shader.uniforms.triplanarNormalMap = { value: mat.normalMap }; 
            }
            
            shader.vertexShader = `
                varying vec3 vWorldPosition;
                varying vec3 vTriplanarNormal; // Use a distinct name for world-space normal
                ${shader.vertexShader}
            `;
            shader.vertexShader = shader.vertexShader.replace(
                '#include <begin_vertex>',
                `
                #include <begin_vertex>
                vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
                vTriplanarNormal = normalize(mat3(modelMatrix) * normal); // Transform normal to world space
                `
            );
  
            shader.fragmentShader = `
                uniform float triplanarScale;
                uniform sampler2D triplanarTexture;
                varying vec3 vWorldPosition;
                varying vec3 vTriplanarNormal; // Use the distinct name in fragment shader too
                ${shader.fragmentShader}
            `;
  
            const triplanarSampleFunction = `
                vec4 sampleTriplanarTexture(sampler2D tex, vec3 p, vec3 n, float scale) {
                    vec3 blend_weights = abs(n);
                    blend_weights = normalize(max(vec3(0.001), blend_weights));
                    blend_weights /= (blend_weights.x + blend_weights.y + blend_weights.z);
  
                    vec4 colX = texture2D(tex, p.yz * scale);
                    vec4 colY = texture2D(tex, p.xz * scale);
                    vec4 colZ = texture2D(tex, p.xy * scale);
  
                    return colX * blend_weights.x + colY * blend_weights.y + colZ * blend_weights.z;
                }
            `;
  
            shader.fragmentShader = shader.fragmentShader.replace(
                'void main() {',
                `${triplanarSampleFunction}\nvoid main() {`
            );
  
            // Replace the standard map lookup with our tri-planar lookup
            shader.fragmentShader = shader.fragmentShader.replace(
                '#include <map_fragment>',
                `
                vec4 texelColor = sampleTriplanarTexture(triplanarTexture, vWorldPosition, vTriplanarNormal, triplanarScale);
  
                diffuseColor *= texelColor; // Apply the triplanar texture to diffuseColor
                `
            );
        }
        
        shader.fragmentShader = shader.fragmentShader.replace(
            '#include <output_fragment>',
            `
            #ifdef OPAQUE
            gl_FragColor = vec4( outgoingLight, opacity );
            #else
            gl_FragColor = vec4( outgoingLight, diffuseColor.a ); // Use diffuseColor.a for transparency if applicable
            #endif
  
            if (!gl_FrontFacing) {
                vec3 backfaceColor = vec3( 0.4, 0.4, 0.4 );
                gl_FragColor = vec4( backfaceColor, opacity ); // Ensure opacity is used for backface
            }
            `
        );
        mat.userData.shader = shader;
    };

1 Answer 1

0

I was able to find my answer.

First of all instead of having my texture starting from the middle, I had it starting from a corner to make it easier to solve. Then I sent the mesh into the function findOffset() to calculate the offset I needed to add/remove in the shader.

function findOffset(mesh, material, options = {}) {
    const autoScale    = options.autoScale ?? false;   // scale texture to fit mesh
    const uniformScale = options.scale     ?? material.userData.ratio;     // manual scale multiplier
    if (!material.userData.shader) {
        console.warn("Shader not yet compiled. Call this AFTER first render.");
        return;
    }
    const shader = material.userData.shader;
    
    // Compute world-space bounding box
    const bbox = new THREE.Box3().setFromObject(mesh);
    const size = bbox.getSize(new THREE.Vector3());
    let min  = bbox.min;     // bottom-left-near corner (world-space!)
    min.x = min.x * uniformScale;
    min.y = min.y * uniformScale;
    min.z = min.z * uniformScale;
    
    // Compute offset for back sides
    const back = size.clone().multiplyScalar(uniformScale);
    
    // Pass to shader
    shader.uniforms.triplanarScaleX = { value: uniformScale };
    shader.uniforms.triplanarScaleY = { value: uniformScale };
    shader.uniforms.triplanarScaleZ = { value: uniformScale };
    shader.uniforms.triplanarOffset.value.copy(min);
    shader.uniforms.triplanarOffsetBack.value.copy(back);
    
    shader.uniforms.triplanarScaleX.value = uniformScale
    shader.uniforms.triplanarScaleY.value = uniformScale
    shader.uniforms.triplanarScaleZ.value = uniformScale
}

Finaly in the shader, I added the offset.

vec4 sampleTriplanarTexture(sampler2D tex, vec3 p, vec3 n, vec3 triplanarOffset, vec3 triplanarOffsetBack, float triplanarScaleX, float triplanarScaleY, float triplanarScaleZ) {
                    vec3 blend_weights = abs(n);
                    vec2 uvX;
                    vec2 uvY;
                    vec2 uvZ;
                    
                    vec4 colX;
                    vec4 colY;
                    vec4 colZ;
                    
                    vec3 N = normalize(n);
                    vec3 V = normalize(vViewPosition);
                    
                    // Decide dominant axis (stable per face)
                    vec3 w = abs(N);
                    bool faceX = w.x > w.y && w.x > w.z;
                    bool faceY = w.y > w.x && w.y > w.z;
                    bool faceZ = w.z > w.x && w.z > w.y;
                    
                    bool back = false;
                    if (faceZ && N.z < 0.0) { 
                        back = true;
                    }
                
                    if (faceX && N.x < 0.0) { 
                        back = true;
                    }
                
                    if (faceY && N.y < 0.0) {
                        back = true;
                    }
                    
                    // Identify cube face by normal
                    if (back == false) { // FRONT
                        blend_weights = normalize(max(vec3(0.001), blend_weights));
                        blend_weights /= (blend_weights.x + blend_weights.y + blend_weights.z);
                        
                        uvX = vec2( p.y * triplanarScaleY, p.z * triplanarScaleZ ) + triplanarOffset.yz;
                        uvY = vec2( p.x * triplanarScaleX, p.z * triplanarScaleZ ) + triplanarOffset.xz;
                        uvZ = vec2( p.x * triplanarScaleX, p.y * triplanarScaleY ) + triplanarOffset.xy;
                        
                        uvX.x = 1.0 - uvX.x;    // left/right border horizontal mirror
                        uvX.y = 1.0 - uvX.y;    // left/right border horizontal mirror
                        uvY.y = 1.0 - uvY.y;   // front border vertical flip
                        
                        colX = texture2D(tex, uvX);
                        colY = texture2D(tex, uvY);
                        colZ = texture2D(tex, uvZ);
                    }else{ //BACK
                        blend_weights = normalize(max(vec3(0.001), blend_weights));
                        blend_weights /= (blend_weights.x + blend_weights.y + blend_weights.z);
                        
                        uvX = vec2( p.y * triplanarScaleY, p.z * triplanarScaleZ ) + triplanarOffset.yz;
                        uvY = vec2( p.x * triplanarScaleX, p.z * triplanarScaleZ ) + triplanarOffset.xz;
                        uvZ = vec2( p.x * triplanarScaleX, p.y * triplanarScaleY ) + triplanarOffset.xy;                        
                        
                        uvX.y = 1.0 - uvX.y;   // left/right border horizontal mirror
                        uvZ.y = 1.0 - uvZ.y;   // front border vertical flip
                        
                        uvZ += vec2(0.0, triplanarOffsetBack.z); //back side offset
                        uvX += vec2(triplanarOffsetBack.z, 0.0); //font side offset
                        
                        colX = texture2D(tex, uvX);
                        colY = texture2D(tex, uvY);
                        colZ = texture2D(tex, uvZ);
                    }

                    return colX * blend_weights.x + colY * blend_weights.y + colZ * blend_weights.z;
                }

Which give the expected result:
Before

After

New contributor
Idea is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.