【问题标题】:Raycast in Three.js with only a projection matrixThree.js 中的 Raycast,只有一个投影矩阵
【发布时间】:2020-03-28 11:53:47
【问题描述】:

我在 this example 之后的 Mapbox GL JS 页面中使用 Three.js 渲染一些自定义层。我想添加光线投射来确定用户点击了哪个对象。

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

这渲染得非常好,并在我平移/旋转/缩放地图时跟踪视图的变化。

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

  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.Camera 更改为THREE.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}, ... },
]

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

完整示例in this codepen

【问题讨论】:

  • "有什么方法可以设置matrixWorld"你试过updateMatrixWorld()吗?所有的相机也是 Object3Ds...
  • @Barthy 我尝试在 codepen 中添加 this.camera.updateMatrixWorld(true); 来代替 this.camera.projectionMatrixInverse 行,但无济于事。同样的行为。
  • @Barthy 具体来说,this.camera.matrixWorld 是调用updateMatrixWorld 之前和之后的单位矩阵。
  • 还有一些关于这个 mapbox-gl 问题的有趣材料github.com/mapbox/mapbox-gl-js/issues/7395
  • @abhishekranjan 下面的答案是解决方案,包括。一个功能齐全的 JS Fiddle。如果喜欢,请点赞。晚上我也会看看你的问题。

标签: three.js mapbox-gl-js raycasting


【解决方案1】:

难以调试,但已解决!

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

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

  2. 在调试过程中,我创建了 raycaster 设置函数的简化副本,以控制一切,并获得了回报。

  3. 立方体不是调试的最佳对象,它过于对称。最好的是不对称的基元或复合对象。我更喜欢使用 3D 坐标交叉来直接查看坐标轴方向。

坐标系

  • 无需更改 BoxCustomLayer 构造函数中的 DefaultUp,因为目标是使所有内容都与 Three 中的默认坐标对齐。
  • 您在 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);
  1. 确保从makeScene 中的组中删除缩放,实际上您不再需要该组了。

  2. 无需触摸render函数。

光线投射

这里有点棘手,基本上是Raycaster 会做的,但我省略了一些不必要的函数调用,例如相机世界矩阵是恒等式 => 无需乘以它。

  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. 设置光线投射器光线
this.raycaster.set(cameraPosition, viewDirection);
  1. 其余的可以保持不变。

完整代码

Fiddle 用于演示。

  • 我添加了mousemove 以使用 jQuery 显示实时调试信息。
  • 点击后,有关点击的完整信息会记录到控制台。

【讨论】:

  • 太棒了,谢谢@isolin!不兼容的坐标系真的很烦人。我相信DefaultUp 的变化来自于我为这个小提琴移除的材料和灯光问题。 (我必须将纹理从里到外翻转才能显示出来。)我会看看我是否可以重现它。但无论如何,非常感谢您的解决方案。确实“难以调试”!
  • @Isolin 感谢您的回答!但是,我还没有完全理解它是如何工作的,你能解释一下吗? PerspectiveCamera 的 Raycaster.setFromCamera 方法的工作原理如下: - 它选择原点作为相机的位置(相机的 matrixWorld 的位置分量) - 对于方向,它使用相机的 projectionMatrix(和 matrixWorld)将传递的鼠标坐标取消投影到 3d 空间)。由于 matrixWorld 是我们代码中的标识,因此与您的答案的唯一区别是您也取消了原点的投影。我不明白为什么需要这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-01
  • 2015-11-01
  • 2012-03-27
  • 2013-08-03
  • 1970-01-01
  • 2015-04-11
  • 1970-01-01
相关资源
最近更新 更多