在Three.js中只用一个投影矩阵进行Raycast。

问题描述 投票:12回答:1

我在Mapbox GL JS页面中使用Three.js渲染一些自定义的图层,如下图所示 本例. 我想添加raycasting来确定用户点击了哪个对象。

问题是我只从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);
  }
}

这个渲染效果很好,当我平移缩放地图时,它可以跟踪视图的变化。

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));
  }

这给了我一个异常。

THREE.Raycaster: Unsupported camera type.

我可以从一个普通的... THREE.CameraTHREE.PerspectiveCamera 而不影响场景的渲染。

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

这就解决了这个异常,但也不会导致任何对象被记录下来。仔细研究一下就会发现,摄像机的 projectionMatrixInverse 是所有 NaNs,我们可以通过计算来解决这个问题。

  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}, ... },
]

所以很明显这里有问题 看一下 编码 setCamera它既涉及 projectionMatrixmatrixWorld. 有什么方法可以让我设置 matrixWorld还是直接使用投影矩阵来构造射线施放者的射线?似乎我只需要投影矩阵来渲染场景,所以我希望它也是我投射射线所需要的全部。

完整的例子 在此笔下.

three.js mapbox-gl-js raycasting
1个回答
4
投票

难以调试,但已经解决了!

TL;DR:完整的代码在这个工作 小提琴.

  1. 我认为没有MapBox的相机世界矩阵不是主要问题,而是坐标空间的不兼容。MapBox提供的是一个左手系统,它有 z 指的是上。三是采用右手系统。y 指向上方。

  2. 在调试过程中,我创建了一个缩小的raycaster设置函数的副本,以使一切都在控制之下,它得到了回报。

  3. 立方体不是调试的最佳对象,它太对称了。最好的是不对称的基元或复合对象。我更喜欢用三维坐标交叉来直接看到轴的方向。

坐标系统

  • 没有必要改变 DefaultUpBoxCustomLayer 构造函数,因为我们的目标是让所有的东西都与 Three 中的默认坐标对齐。
  • 您可以在 onAdd() 方法,但是在初始化组的时候也要对所有对象进行缩放。这样一来,你在反转投影后的坐标就会在 raycast() 不再以米为单位了。因此,让我们将缩放部分与 this.cameraTransform. 左手到右手的转换也应该在这里完成。我决定将z轴翻转,并沿x轴旋转90deg,得到一个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);
  1. 一定要把这组中的比例尺去掉。makeScene ,其实你已经不需要这个组了。

  2. 不用再去碰 render 功能。

射线广播

这里就有点棘手了,基本上是什么?Raycaster 就可以了,但我省略了一些不必要的函数调用,例如相机世界矩阵是identity =&gt,不需要与它相乘。

  1. 获取投影矩阵的逆。它不更新时 Object3D.projectionMatrix 已被分配,所以让我们手动计算它。
  2. 用它来获取3D中的摄像头和鼠标位置。这相当于 Vector3.unproject.
  3. viewDirection 是简单的归一化向量,从 cameraPositionmousePosition.
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();
  1. 设置Raycaster射线
this.raycaster.set(cameraPosition, viewDirection);
  1. 其他的可以保持不变。

完整代码

小提琴 为示范。

  • 我加了 mousemove 来显示实时调试信息 jQuery.
  • 点击后,关于命中的完整信息会被记录到控制台。
© www.soinside.com 2019 - 2024. All rights reserved.