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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 11:33:00