Three.js中如何为单个.obj 3D人体模型指定部位添加点击事件监听器
基于Three.js生态的实现方案
你遇到的是单模型多部位交互的典型场景,核心思路是通过射线检测(Raycaster)配合自定义部位标记实现,不需要拆分原OBJ模型,具体步骤如下:
1. 提前给模型部位做标记
OBJ导入后通常分为两种情况,按需处理即可:
- 如果导入后是单个Mesh:在Blender等3D编辑工具中打开原模型,给眼睛、鼻子、手臂等需要交互的部位的面分别分配独立的
materialIndex,或者给对应面附加自定义userData属性标记部位名称,导出OBJ时保留自定义属性即可。 - 如果导入后是Group下的多个子Mesh:直接给对应部位的子Mesh添加自定义标记即可,示例代码:
armMesh.userData.partName = 'leftArm'
2. 实现射线点击检测逻辑
Three.js内置的Raycaster可以计算点击位置与3D模型的相交面,配合提前打好的标记就能识别点击的部位:
// 初始化射线工具和鼠标坐标向量 const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); // 提前配置materialIndex和部位的映射表(单Mesh场景用) const partMap = {0: 'leftEye', 1: 'rightEye', 2: 'nose', 3: 'leftArm'} // 点击事件回调 function onModelClick(event) { // 转换为标准化设备坐标 mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 更新射线起点方向 raycaster.setFromCamera(mouse, camera); // 递归检测人体模型所有子元素的相交情况 const intersects = raycaster.intersectObject(humanModel, true); if (intersects.length > 0) { const firstHit = intersects[0]; let targetPart = '' // 多子Mesh场景读自定义标记 if (firstHit.object.userData.partName) { targetPart = firstHit.object.userData.partName } // 单Mesh场景读面的materialIndex匹配部位 else { targetPart = partMap[firstHit.face.materialIndex] || '' } // 匹配到部位就触发弹窗 if (targetPart) { showCustomPopup(targetPart) // 替换为你自己的弹窗逻辑 } } }
3. 处理OrbitControls的点击冲突
默认情况下点击拖拽会触发OrbitControls的旋转逻辑,很容易误触发点击事件,加个简单的拖拽判定就能解决:
let mouseDownPos = {x: 0, y: 0} const clickThreshold = 3 // 点击判定的最大移动像素值 renderer.domElement.addEventListener('mousedown', (e) => { mouseDownPos.x = e.clientX mouseDownPos.y = e.clientY }) renderer.domElement.addEventListener('mouseup', (e) => { const moveX = Math.abs(e.clientX - mouseDownPos.x) const moveY = Math.abs(e.clientY - mouseDownPos.y) // 只有点击前后鼠标移动距离小于阈值才判定为有效点击 if (moveX < clickThreshold && moveY < clickThreshold) { onModelClick(e) } })
可选性能优化
如果你的人体模型面数很高,射线检测有性能损耗,可以额外做一套低模碰撞盒:制作一个和原人体部位完全对齐的低多边形模型,放在原模型同一位置设置visible: false,射线只检测这个不可见的低模即可,能大幅提升检测效率。
内容的提问来源于stack exchange,提问作者Naushad
相关产品推荐
相关产品推荐

