THREE.js中SVG挤出模型无法显示法线贴图(Normal Map)等纹理的问题求助
THREE.js中SVG挤出模型无法显示法线贴图(Normal Map)等纹理的问题求助
嘿,你的猜测完全没错——UV映射大概率就是问题所在!从SVG挤出的模型,默认的UV坐标生成逻辑往往没法很好适配纹理贴图,尤其是你用了THREE.ExtrudeGeometry.WorldUVGenerator,这个生成器是基于世界坐标生成UV的,再加上你后续对模型做了缩放、旋转操作,很容易导致UV被拉伸、错位,最终让法线/粗糙度贴图无法正确显示。
给你几个具体的解决方案,你可以逐个尝试:
1. 自定义UV生成器
替换默认的WorldUVGenerator,针对SVG挤出的顶面和侧面分别生成合理的UV:
// 自定义UV生成器,适配挤出模型的不同面 class CustomUVGenerator { generateTopUV(geometry, vertices, indexA, indexB, indexC) { const box = geometry.boundingBox; const sizeX = box.max.x - box.min.x; const sizeY = box.max.y - box.min.y; // 基于模型的XY平面尺寸计算顶面UV const uvA = new THREE.Vector2((vertices[indexA * 3] - box.min.x) / sizeX, (vertices[indexA * 3 + 1] - box.min.y) / sizeY); const uvB = new THREE.Vector2((vertices[indexB * 3] - box.min.x) / sizeX, (vertices[indexB * 3 + 1] - box.min.y) / sizeY); const uvC = new THREE.Vector2((vertices[indexC * 3] - box.min.x) / sizeX, (vertices[indexC * 3 + 1] - box.min.y) / sizeY); return [uvA, uvB, uvC]; } generateSideWallUV(geometry, vertices, indexA, indexB, indexC, indexD) { const box = geometry.boundingBox; const depth = box.max.z - box.min.z; // 基于模型的挤出深度计算侧面UV const uvA = new THREE.Vector2((vertices[indexA * 3] - box.min.x) / (box.max.x - box.min.x), (vertices[indexA * 3 + 2] - box.min.z) / depth); const uvB = new THREE.Vector2((vertices[indexB * 3] - box.min.x) / (box.max.x - box.min.x), (vertices[indexB * 3 + 2] - box.min.z) / depth); const uvC = new THREE.Vector2((vertices[indexC * 3] - box.min.x) / (box.max.x - box.min.x), (vertices[indexC * 3 + 2] - box.min.z) / depth); const uvD = new THREE.Vector2((vertices[indexD * 3] - box.min.x) / (box.max.x - box.min.x), (vertices[indexD * 3 + 2] - box.min.z) / depth); return [uvA, uvB, uvC, uvD]; } }
然后在创建ExtrudeGeometry时替换UVGenerator:
const geometry = new THREE.ExtrudeGeometry( tagName === "rect" ? scaledShape : shape, { depth: 10000, bevelEnabled: tagName === "rect", bevelSize: 8, UVGenerator: new CustomUVGenerator(), // 替换为自定义生成器 bevelThickness: 2, bevelSegments: 1, bevelOffset: -8, } );
2. 手动计算并调整UV坐标
如果自定义生成器太麻烦,也可以在创建geometry后手动修改UV属性:
geometry.computeBoundingBox(); const box = geometry.boundingBox; const sizeX = box.max.x - box.min.x; const sizeY = box.max.y - box.min.y; const sizeZ = box.max.z - box.min.z; const uvAttribute = geometry.getAttribute('uv'); for (let i = 0; i < uvAttribute.count; i++) { const vertex = new THREE.Vector3().fromBufferAttribute(geometry.getAttribute('position'), i); let u, v; // 判断顶点属于顶面/底面还是侧面,分别计算UV if (Math.abs(vertex.z - box.min.z) < 0.1 || Math.abs(vertex.z - box.max.z) < 0.1) { // 顶面/底面用XY平面坐标计算 u = (vertex.x - box.min.x) / sizeX; v = (vertex.y - box.min.y) / sizeY; } else { // 侧面用XZ平面坐标计算 u = (vertex.x - box.min.x) / sizeX; v = (vertex.z - box.min.z) / sizeZ; } uvAttribute.setXY(i, u, v); } uvAttribute.needsUpdate = true;
3. 检查纹理的重复与缩放设置
有时候纹理的包裹模式不对也会导致显示异常,你可以给纹理加上重复设置:
// 加载法线贴图时设置重复模式 const normalMap = textureLoader.load(`/textures/plastic/normal.webp`); normalMap.wrapS = THREE.RepeatWrapping; normalMap.wrapT = THREE.RepeatWrapping; normalMap.repeat.set(1, 1); // 可根据模型大小调整重复次数 // 粗糙度贴图同理 const roughnessMap = textureLoader.load(`/textures/plastic/roughness.webp`); roughnessMap.wrapS = THREE.RepeatWrapping; roughnessMap.wrapT = THREE.RepeatWrapping; roughnessMap.repeat.set(1, 1); // 再赋值给材质 material.normalMap = normalMap; material.roughnessMap = roughnessMap;
另外还有个小细节:你当前是对mesh做了0.001的缩放,这可能会间接影响UV的显示效果。建议改成直接缩放geometry,这样UV计算会更准确:
// 替换 mesh.scale.set(0.001, 0.001, 0.001); geometry.scale(0.001, 0.001, 0.001);
你可以先试试自定义UV生成器或者手动调整UV的方法,应该能解决大部分问题。如果还是不行,可以告诉我具体的异常现象(比如纹理完全不显示、错位还是拉伸),我再帮你进一步排查~
备注:内容来源于stack exchange,提问作者Robin G
相关产品推荐
相关产品推荐

