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

如何让Three.js黄色圆形网格贴合GLTF衬衫模型并实现覆盖

问题:如何让黄色圆形网格贴合GLTF衬衫模型曲面实现完全覆盖?

当前通过Raycaster实现了网格始终悬浮在衬衫上方,但无法随衬衫曲面产生弯曲效果,只能保持平面状态。

当前效果:
当前效果(带圆形网格的衬衫)

期望效果:
预期最终效果 另一角度效果

当前实现代码

//shirt = gltf model
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects;
var normalMatrix = new THREE.Matrix3();
var worldNormal = new THREE.Vector3();
var lookAtVector = new THREE.Vector3();
var dragging = false;
//events
window.addEventListener("mousemove", onMouseMove, false);
window.addEventListener("mousedown", onMouseDown, false);
window.addEventListener("mouseup", onMouseUp, false);

function onMouseDown(event) {
    let intersects = raycaster.intersectObjects([cube]);
    if (intersects.length > 0) {
        controls.enableRotate = false;
        dragging = true;
    }
}

function onMouseUp(event) {
    controls.enableRotate = true;
    dragging = false;
}

function onMouseMove(event) {
    mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
    raycaster.setFromCamera(mouse, camera);
    intersects = raycaster.intersectObjects([shirt.scene.children[0]]);

    if (intersects.length == 0 || !dragging) return;

    normalMatrix.getNormalMatrix(intersects[0].object.matrixWorld);
    worldNormal.copy(intersects[0].face.normal).applyMatrix3(normalMatrix).normalize();
    cube.position.copy(intersects[0].point);
    cube.lookAt(lookAtVector.copy(intersects[0].point).add(worldNormal));
    cube.geometry.computeVertexNormals();
}

解决方案

要让网格完全贴合衬衫曲面,核心是让网格的每个顶点都精准对应衬衫表面的位置,而不是仅移动整个网格的位置和旋转。以下是两种可行方案:

方案一:逐个顶点射线投射贴合

1. 初始化保存网格顶点局部偏移

先记录圆形网格每个顶点相对于中心的初始位置,后续以此为基准计算偏移:

// 初始化时执行,保存原始顶点的局部偏移
const originalVertices = [];
const cubeGeo = cube.geometry;
cubeGeo.vertices.forEach(v => {
  originalVertices.push(v.clone());
});

2. 实时更新每个顶点位置

在鼠标拖动时,对每个顶点单独进行射线检测,找到其在衬衫表面的对应点,更新顶点坐标:

function onMouseMove(event) {
  mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
  raycaster.setFromCamera(mouse, camera);
  intersects = raycaster.intersectObjects([shirt.scene.children[0]]);

  if (intersects.length == 0 || !dragging) return;

  // 获取网格中心在衬衫上的交点与法线
  normalMatrix.getNormalMatrix(intersects[0].object.matrixWorld);
  worldNormal.copy(intersects[0].face.normal).applyMatrix3(normalMatrix).normalize();
  const centerPoint = intersects[0].point.clone();

  // 遍历更新每个顶点
  const cubeGeo = cube.geometry;
  for (let i = 0; i < cubeGeo.vertices.length; i++) {
    // 将局部顶点偏移转换为世界坐标系下的位置
    const localOffset = originalVertices[i].clone();
    localOffset.applyQuaternion(cube.quaternion);
    const rayOrigin = centerPoint.clone().add(localOffset);
    
    // 从顶点位置向衬衫内部投射射线,找到贴合点
    raycaster.set(rayOrigin, worldNormal.multiplyScalar(-1));
    const vertexIntersects = raycaster.intersectObjects([shirt.scene.children[0]]);
    
    if (vertexIntersects.length > 0) {
      // 将世界坐标系下的交点转换回网格局部坐标系
      cubeGeo.vertices[i].copy(vertexIntersects[0].point.clone().sub(centerPoint));
    }
  }

  // 更新网格位置、旋转并标记顶点更新
  cube.position.copy(centerPoint);
  cube.lookAt(lookAtVector.copy(centerPoint).add(worldNormal));
  cubeGeo.verticesNeedUpdate = true;
  cubeGeo.computeVertexNormals();
}

注意事项

  • 该方法会频繁执行射线检测,若网格顶点过多会导致性能下降,建议降低圆形网格的细分度。
  • 确保衬衫模型的几何体面法线方向正确,否则射线检测可能失效。

方案二:使用Three.js内置DecalGeometry(推荐)

Three.js提供了DecalGeometry,专门用于创建贴合任意曲面的贴纸/网格,实现成本更低且效果更稳定:

1. 初始化Decal网格

// 创建黄色材质(可根据需求调整透明度、颜色)
const decalMaterial = new THREE.MeshBasicMaterial({ 
  color: 0xffff00, 
  transparent: true,
  opacity: 0.8
});
// 初始DecalGeometry(参数:目标模型、中心位置、法线方向、网格尺寸)
const initialCenter = new THREE.Vector3(0, 0, 0);
const initialNormal = new THREE.Vector3(0, 1, 0);
const decalGeometry = new THREE.DecalGeometry(
  shirt.scene.children[0],
  initialCenter,
  initialNormal,
  new THREE.Vector3(6, 6, 0) // 调整尺寸匹配你的需求
);
const decalMesh = new THREE.Mesh(decalGeometry, decalMaterial);
scene.add(decalMesh);

2. 拖动时更新Decal位置与形态

function onMouseMove(event) {
  mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
  raycaster.setFromCamera(mouse, camera);
  intersects = raycaster.intersectObjects([shirt.scene.children[0]]);

  if (intersects.length == 0 || !dragging) return;

  // 获取当前交点与法线
  normalMatrix.getNormalMatrix(intersects[0].object.matrixWorld);
  worldNormal.copy(intersects[0].face.normal).applyMatrix3(normalMatrix).normalize();
  const centerPoint = intersects[0].point.clone();

  // 销毁旧的几何体并创建新的DecalGeometry
  decalMesh.geometry.dispose();
  decalMesh.geometry = new THREE.DecalGeometry(
    shirt.scene.children[0],
    centerPoint,
    worldNormal,
    new THREE.Vector3(6, 6, 0)
  );
}

该方案无需手动处理顶点,由Three.js内部完成曲面贴合计算,性能和效果更优。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 03:25:04