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
相关产品推荐
相关产品推荐

