You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js中Shader实现淋浴底座矿物纹理边缘对齐问题求助

三平面纹理边缘对齐问题的解决方案

核心问题分析

当前代码使用世界空间坐标进行三平面采样,当模型存在缩放、不同面坐标范围差异时,会导致纹理在边缘处无法对齐。比如立方体各轴坐标范围一致,但采样时不同面的纹理坐标起始逻辑不统一,最终出现拼接错位。


针对性解决方案

1. 改用局部空间坐标采样

放弃世界空间,直接使用模型的局部空间原始顶点坐标进行采样,纹理将基于模型自身坐标系对齐,不受模型缩放、平移操作的影响。

修改顶点着色器:

// 替换原vWorldPosition的定义与赋值
varying vec3 vLocalPosition;

// 在begin_vertex段后添加
vLocalPosition = position; // 直接使用模型局部空间坐标

修改片段着色器的采样调用:

vec4 texelColor = sampleTriplanarTexture(triplanarTexture, vLocalPosition, vTriplanarNormal, triplanarScale);

2. 绑定纹理缩放与模型尺寸

由于产品尺寸支持动态调整,需要让纹理密度保持一致。可基于模型**包围盒(Bounding Box)**计算缩放值,确保不同尺寸模型上的纹理显示比例统一:

// 初始化材质前,计算模型包围盒
const box = new THREE.Box3().setFromObject(mesh);
const size = new THREE.Vector3();
box.getSize(size);
const maxDim = Math.max(size.x, size.y, size.z);
// 设置缩放值,示例为最大边长方向重复5次纹理
mat.userData.triplanarScale = 5 / maxDim;

3. 优化三平面混合权重

当前混合权重计算过于生硬,改用平滑过渡逻辑,减少边缘拼接痕迹:

const triplanarSampleFunction = `
    vec4 sampleTriplanarTexture(sampler2D tex, vec3 p, vec3 n, float scale) {
        vec3 blend_weights = pow(abs(n), vec3(2.0)); // 用pow增强权重,过渡更自然
        blend_weights = normalize(max(blend_weights, vec3(0.001)));
        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;
    }
`;

4. 处理切割后的几何体

若几何体经过切割(如CSG操作),需确保切割面法线正确归一化且方向无误,同时保证局部坐标连续性。切割完成后重新计算包围盒,调整纹理缩放值即可适配新的几何体形态。


修改后的完整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 vLocalPosition;
            varying vec3 vTriplanarNormal;
            ${shader.vertexShader}
        `;
        shader.vertexShader = shader.vertexShader.replace(
            '#include <begin_vertex>',
            `
            #include <begin_vertex>
            vLocalPosition = position; // 使用模型局部空间坐标
            vTriplanarNormal = normalize(mat3(modelMatrix) * normal);
            `
        );

        shader.fragmentShader = `
            uniform float triplanarScale;
            uniform sampler2D triplanarTexture;
            varying vec3 vLocalPosition;
            varying vec3 vTriplanarNormal;
            ${shader.fragmentShader}
        `;

        const triplanarSampleFunction = `
            vec4 sampleTriplanarTexture(sampler2D tex, vec3 p, vec3 n, float scale) {
                vec3 blend_weights = pow(abs(n), vec3(2.0));
                blend_weights = normalize(max(blend_weights, vec3(0.001)));
                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() {`
        );

        shader.fragmentShader = shader.fragmentShader.replace(
            '#include <map_fragment>',
            `
            vec4 texelColor = sampleTriplanarTexture(triplanarTexture, vLocalPosition, vTriplanarNormal, triplanarScale);
            diffuseColor *= texelColor;
            `
        );
    }
    
    shader.fragmentShader = shader.fragmentShader.replace(
        '#include <output_fragment>',
        `
        #ifdef OPAQUE
        gl_FragColor = vec4( outgoingLight, opacity );
        #else
        gl_FragColor = vec4( outgoingLight, diffuseColor.a );
        #endif

        if (!gl_FrontFacing) {
            vec3 backfaceColor = vec3( 0.4, 0.4, 0.4 );
            gl_FragColor = vec4( backfaceColor, opacity );
        }
        `
    );
    mat.userData.shader = shader;
};

内容的提问来源于stack exchange,提问作者Idea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 00:05:53