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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 04:25:16