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

