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:
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 of having the texture align.
Here is the current code of the shader:
mat.onBeforeCompile = function( shader ) {
// --- Custom Uniforms and Varyings for Tri-planar ---
if (mat.map) { // Only apply tri-planar if the material has a texture map
shader.uniforms.triplanarTexture = { value: mat.map }; // The texture itself
shader.uniforms.triplanarScale = mat.userData.triplanarScale;
// *** NEW: Add Uniform for Normal Map ***
if (mat.normalMap) {
shader.uniforms.triplanarNormalMap = { value: mat.normalMap };
}
// Declare our own custom varyings to avoid conflicts
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
`
);
// --- Fragment Shader Modifications for Tri-planar ---
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);
// Add a small epsilon to avoid division by zero for perfectly axis-aligned normals
blend_weights = normalize(max(vec3(0.001), blend_weights));
blend_weights /= (blend_weights.x + blend_weights.y + blend_weights.z);
// Project on YZ (X-axis dominant)
vec4 colX = texture2D(tex, p.yz * scale);
// Project on XZ (Y-axis dominant)
vec4 colY = texture2D(tex, p.xz * scale);
// Project on XY (Z-axis dominant)
vec4 colZ = texture2D(tex, p.xy * scale);
// Blend based on normal weights
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>',
`
// Calculate texture color using tri-planar mapping
// Use vTriplanarNormal here!
vec4 texelColor = sampleTriplanarTexture(triplanarTexture, vWorldPosition, vTriplanarNormal, triplanarScale);
// Use texelColor instead of standard map lookup
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
// Apply backface color after the primary fragment output,
// but before any final adjustments like alpha blending if not already handled.
// This directly overrides gl_FragColor for backfaces.
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;
};






