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