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

Three.js如何正确配置纹理投影 解决挤出曲面纹理拉伸问题

Three.js沿路径挤出曲面纹理拉伸问题

效果截图
沿椭圆路径挤出制作3/4圆柱曲面时,加载纹理后没有均匀贴合曲面,出现明显拉伸变形。已知问题和纹理投影设置相关,但不清楚具体调整方式。

复现代码

class EllipseCurve3 extends THREE.Curve {
  
  ellipse = null
 
  constructor (ellipse) {
    super()
    this.ellipse = ellipse
  }
  
  
  getPoint(t, optionalTarget = new THREE.Vector3()) {
    const point = this.ellipse.getPoint(t, optionalTarget)
    return new THREE.Vector3(
      point.x,
      point.y,
      0
    )
  }
}

// 初始化场景
const scene = new THREE.Scene();

var shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.moveTo(0, 1);
shape.lineTo(50, 1);
shape.moveTo(50, 0);
shape.lineTo(0, 0);

const arc = new THREE.EllipseCurve(
    0,
    0, // ax, aY
    100,
    100, // xRadius, yRadius
    0,
    1.5 * Math.PI, // aStartAngle, aEndAngle
    false, // aClockwise
    0 // aRotation
  ),
  path = new EllipseCurve3(arc),
  geometry = new THREE.ExtrudeGeometry(shape, {
    bevelEnabled: false,
    extrudePath: path,
    steps: 50,
    depth: 5,
    amount: 20,
    material: 0,
    extrudeMaterial: 1
  });


// 配置灯光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(100, 200, 100);
scene.add(directionalLight);

// 配置相机
const width = 200;
const height = width * (window.innerHeight / window.innerWidth);
const camera = new THREE.OrthographicCamera(
  width / -2,
  width / 2,
  height / 2,
  height / -2,
  0.1,
  1000
);

camera.position.set(400, 400, 400);
camera.lookAt(0, 0, 0);

// 配置渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerWidth);

const controls = new THREE.OrbitControls(camera, renderer.domElement);

renderer.render(scene, camera);

document.body.appendChild(renderer.domElement);
var textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = true;

const picture = 'https://threejs.org/examples/textures/uv_grid_opengl.jpg'
textureLoader.load(picture, function(texture) {
    texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
    texture.repeat.set(.01, .01);

    var material = new THREE.MeshBasicMaterial({
        map: texture,
        needsUpdate: true,
    });
  
  var mesh = new THREE.Mesh(geometry, material)
  mesh.rotation.x = Math.PI / 2
  scene.add(mesh)
})

function render() {
  renderer.render(scene, camera);
  controls.update();
  requestAnimationFrame(render);
}
render();

解决方法

纹理拉伸的核心原因是ExtrudeGeometry默认生成UV时,沿挤出路径方向直接按参数t(0到1线性取值)映射,没有按实际弧长均匀分配UV,再加上原代码里2D轮廓绘制存在冗余moveTo操作生成了多余顶点,进一步打乱了UV对应关系。
两种修复方案按需选择:

  • 方案一:手动重算几何体UV
    挤出完成后遍历所有顶点,对圆柱面上的点,用顶点相对于圆心的角度值归一化作为U坐标,用顶点沿圆柱高度方向的位置归一化作为V坐标,完全匹配曲面弧度,不会出现拉伸。
  • 方案二:直接用CylinderGeometry生成目标曲面(推荐)
    要做标准3/4圆柱面不需要自定义挤出,Three.js自带的CylinderGeometry原生支持指定扫描角度,生成的UV默认按弧长均匀分布,不会有纹理拉伸问题,代码更简洁:
    // 参数依次为:顶面半径、底面半径、圆柱高度、径向分段数、高度分段数、是否开放面、起始角度、扫描角度
    const geometry = new THREE.CylinderGeometry(100, 100, 50, 50, 1, true, 0, 1.5 * Math.PI)
    

原代码里的shape绘制存在冗余,连续绘制轮廓时按顺序连线闭合即可,不需要重复调用moveTo移动起点,避免生成多余顶点干扰UV计算。

内容的提问来源于stack exchange,提问作者许春风

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:36:21