使用Three.js+Expo GL实现Expo原生App中3D物体触摸检测问题
问题:Expo GL + Three.js 触摸物体检测无相交结果
我正在开发一款Expo原生App,使用Three.js和Expo GL,未使用fiber与drei包。场景内包含若干球体,希望实现触摸场景内物体的检测功能。
场景渲染代码
<GLView {...panResponder.panHandlers} style={{ flex: 1 }} onContextCreate={onContextCreate} onTouchStart={handleTouch} /> const handleTouch = (event) => { const { locationX, locationY } = event.nativeEvent; touch.x = (locationX / event.target.clientWidth) * 2 - 1; touch.y = -(locationY / event.target.clientHeight) * 2 + 1; raycaster.setFromCamera(touch, cameraRef.current); const intersects = raycaster.intersectObject(planet);; if (intersects.length > 0) { console.log(intersects); } };
Planet类定义
class Planet extends Mesh { constructor() { super( new SphereGeometry(1.0, 32, 32), new MeshStandardMaterial({ map: new TextureLoader().load(require("../../icon.jpg")), }) ); } }
当前代码能检测到场景触摸事件,但无论触摸何处都无法找到相交结果,请问遗漏了什么?
排查与解决方法
修正射线检测的参数使用
目前你调用的raycaster.intersectObject(planet)只会检测单个对象本身,如果planet是父容器的子元素,或者你有多个Planet实例,需要调整参数:- 若要递归检测目标对象的所有子元素,添加第二个参数
true:const intersects = raycaster.intersectObject(planet, true); - 若场景中有多个Planet对象,建议将它们统一放入
Group,再使用intersectObjects(复数形式)检测所有子对象:// 初始化时创建Group并添加所有Planet const planetsGroup = new THREE.Group(); planetsGroup.add(new Planet(), new Planet()); scene.add(planetsGroup); // 触摸检测时 const intersects = raycaster.intersectObjects(planetsGroup.children, true);
- 若要递归检测目标对象的所有子元素,添加第二个参数
确认相机配置与场景添加逻辑
- 确保
cameraRef.current是正确初始化的相机(如PerspectiveCamera),且相机的fov、aspect、near、far参数合理,没有将Planet排除在可视范围内。 - 必须将创建的Planet实例添加到Three.js场景中,否则射线无法检测到:
const scene = new THREE.Scene(); const planet = new Planet(); scene.add(planet); // 此步骤不可省略
- 确保
修正触摸坐标转换逻辑
event.target.clientWidth/clientHeight在Expo环境中可能无法准确获取GLView的实际尺寸,建议通过onLayout回调提前获取视图尺寸:const [viewSize, setViewSize] = useState({ width: 0, height: 0 }); <GLView {...panResponder.panHandlers} style={{ flex: 1 }} onContextCreate={onContextCreate} onTouchStart={handleTouch} onLayout={(e) => { setViewSize({ width: e.nativeEvent.layout.width, height: e.nativeEvent.layout.height }); }} /> // 在handleTouch中使用正确尺寸计算 const handleTouch = (event) => { const { locationX, locationY } = event.nativeEvent; touch.x = (locationX / viewSize.width) * 2 - 1; touch.y = -(locationY / viewSize.height) * 2 + 1; raycaster.setFromCamera(touch, cameraRef.current); const intersects = raycaster.intersectObject(planet, true); if (intersects.length > 0) { console.log(intersects); } };强制更新物体矩阵
如果射线检测是在动画循环外调用,可能物体的矩阵未更新,导致检测失效,可手动强制更新:planet.updateMatrixWorld(true); // 强制更新对象及其子对象的世界矩阵 const intersects = raycaster.intersectObject(planet, true);
内容的提问来源于stack exchange,提问作者Bozidar Milivojevic
相关产品推荐
相关产品推荐

