three.js - Three.js 中的 Raycast 只有一个投影矩阵

标签 three.js mapbox-gl-js raycasting

我正在 Mapbox GL JS 页面中使用 Three.js 渲染一些自定义层,如下 this example .我想添加光线转换来确定用户点击了哪个对象。

问题是我只从 Mapbox 获得了一个投影矩阵,我用它来渲染场​​景:

class CustomLayer {
  type = 'custom';
  renderingMode = '3d';

  onAdd(map, gl) {
    this.map = map;
    this.camera = new THREE.Camera();
    this.renderer = new THREE.WebGLRenderer({
      canvas: map.getCanvas(),
      context: gl,
      antialias: true,
    });
    this.scene = new THREE.Scene();
    // ...
  }

  render(gl, matrix) {
    this.camera.projectionMatrix = new THREE.Matrix4()
      .fromArray(matrix)
      .multiply(this.cameraTransform);
    this.renderer.state.reset();
    this.renderer.render(this.scene, this.camera);
  }
}

这渲染得很好,并在我平移/旋转/缩放 map 时跟踪 View 中的变化。

A cube on Liberty Island

不幸的是,当我尝试添加光线转换时,出现错误:

  raycast(point) {
    var mouse = new THREE.Vector2();
    mouse.x = ( point.x / this.map.transform.width ) * 2 - 1;
    mouse.y = 1 - ( point.y / this.map.transform.height ) * 2;
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, this.camera);
    console.log(raycaster.intersectObjects(this.scene.children, true));
  }

这给了我一个异常(exception):
THREE.Raycaster: Unsupported camera type.

我可以从通用更改 THREE.CameraTHREE.PerspectiveCamera在不影响场景渲染的情况下:

this.camera = new THREE.PerspectiveCamera(28, window.innerWidth / window.innerHeight, 0.1, 1e6);

这修复了异常,但也不会导致记录任何对象。挖掘了一下发现相机的projectionMatrixInverse都是NaN s,我们可以通过计算来修复它:

  raycast(point) {
    var mouse = new THREE.Vector2();
    mouse.x = ( point.x / this.map.transform.width ) * 2 - 1;
    mouse.y = 1 - ( point.y / this.map.transform.height ) * 2;
    this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix);  // <--
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, this.camera);
    console.log(raycaster.intersectObjects(this.scene.children, true));
  }

现在无论我点击哪里,我都会得到两个交叉点,立方体的两个面。它们的距离为 0:
[
  { distance: 0, faceIndex: 10, point: Vector3 { x: 0, y: 0, z: 0 }, uv: Vector2 {x: 0.5, y: 0.5}, ... },
  { distance: 0, faceIndex: 11, point: Vector3 { x: 0, y: 0, z: 0 }, uv: Vector2 {x: 0.5, y: 0.5}, ... },
]

所以很明显有些东西在这里不起作用。看着code for setCamera ,它涉及projectionMatrixmatrixWorld .有没有办法设置matrixWorld ,或仅使用投影矩阵直接构建射线转换器的射线?似乎我只需要投影矩阵来渲染场景,所以我希望它也是我转换光线所需的全部。

完整示例 in this codepen .

最佳答案

调试困难,但解决了!

TL;DR:此工作中的完整代码 Fiddle .

  • 我认为没有 MapBox 相机的世界矩阵不是主要问题,而是坐标空间的不兼容。 Mapbox 提供了一个 z 指向上的左手系统。三使用右手系统,y 指向上方。
  • 在调试过程中,我创建了 raycaster 设置函数的简化副本,以控制一切,并且得到了返回。
  • 立方体不是调试的最佳对象,它太对称了。最好的是非对称基元或复合对象。我更喜欢使用 3D 坐标交叉来直接查看轴方向。

  • 坐标系
  • 无需更改DefaultUpBoxCustomLayer构造函数,因为目标是使所有内容与三中的默认坐标对齐。
  • 您在 onAdd() 中翻转 y 轴方法,但随后您还可以在初始化组时缩放所有对象。这样,您在反转投影后操作的坐标 raycast()不再以米为单位。所以让我们用 this.cameraTransform 加入缩放部分.左手到右手的转换也应该在这里完成。我决定翻转 z 轴并沿 x 轴旋转 90 度以获得 y 向上的右手系:

  • const centerLngLat = map.getCenter();
    this.center = MercatorCoordinate.fromLngLat(centerLngLat, 0);
    const {x, y, z} = this.center;
    
    const s = this.center.meterInMercatorCoordinateUnits();
    
    const scale = new THREE.Matrix4().makeScale(s, s, -s);
    const rotation = new THREE.Matrix4().multiplyMatrices(
            new THREE.Matrix4().makeRotationX(-0.5 * Math.PI),
            new THREE.Matrix4().makeRotationY(Math.PI)); //optional Y rotation
    
    this.cameraTransform = new THREE.Matrix4()
            .multiplyMatrices(scale, rotation)
            .setPosition(x, y, z);
    
  • 确保从 makeScene 中的组中删除缩放。 ,实际上您不再需要该组了。
  • 无需触摸 render功能。

  • 光线转换

    这里有点棘手,基本上就是Raycaster可以,但我省略了一些不必要的函数调用,例如相机世界矩阵是身份 => 无需乘以它。
  • 获取投影矩阵的逆。 Object3D.projectionMatrix时不更新已分配,所以让我们手动计算它。
  • 使用它来获取 3D 中的相机和鼠标位置。这相当于 Vector3.unproject .
  • viewDirection只是来自 cameraPosition 的归一化向量到 mousePosition .

  • const camInverseProjection = 
            new THREE.Matrix4().getInverse(this.camera.projectionMatrix);
    const cameraPosition =
            new THREE.Vector3().applyMatrix4(camInverseProjection);
    const mousePosition =
            new THREE.Vector3(mouse.x, mouse.y, 1)
            .applyMatrix4(camInverseProjection);
    const viewDirection = mousePosition.clone()
            .sub(cameraPosition).normalize();
    
  • 设置 raycaster 射线

  • this.raycaster.set(cameraPosition, viewDirection);
    
  • 其余的可以保持不变。

  • 完整代码

    Fiddle用于演示。
  • 我加了 mousemove使用 jQuery 显示实时调试信息。
  • 单击后,有关命中的完整信息将记录到控制台。
  • 关于three.js - Three.js 中的 Raycast 只有一个投影矩阵,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59163141/

    相关文章:

    java - 射线平方交集

    javascript - A 帧沿相机方向向前移动

    javascript - Three.js Uncaught TypeError : Cannot read property 'addEventListener' of undefined

    ios - iOS 14.0中不推荐使用'hotTest()'

    geojson - Mapbox gl geojson 图层在更高的缩放比例下消失

    meteor - 未捕获的类型错误 : fs. readFileSync 不是函数

    three.js - 如何在threejs中通过raycaster从交叉检查中排除辅助对象?

    Three.js SplineCurve3 无圆边,或 LineCurve3 替代品

    javascript - 在 ThreeJS 中使用新的 PlaneBufferGeometry 访问顶点

    javascript - 如何在 Mapbox GL JS 中搜索矢量图层特征