只有投影矩阵的Three.js中的Raycast

我正在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);
  }
}

这很好渲染,并且可以在平移/旋转/缩放地图时跟踪视图的变化。

只有投影矩阵的Three.js中的Raycast

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

  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就是所有NaN了,我们可以通过计算来解决:

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

很明显,这里有些东西行不通。从code for setCamera看,它涉及projectionmatrixmatrixWorld。有没有一种方法我可以设置matrixWorld或仅使用投影矩阵直接构造光线投射器的光线?看来我只需要投影矩阵即可渲染场景,所以我希望它也将是我投射光线的全部。

完整示例in this codepen

hbsogua 回答:只有投影矩阵的Three.js中的Raycast

难以调试,但已解决!

TL; DR:此有效的Fiddle中的完整代码。

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

  2. 在调试过程中,我创建了raycaster安装程序函数的简化副本,以使所有内容都受到控制,并且还清。

  3. 多维数据集不是最佳的调试对象,它太对称了。最好的是非对称图元或复合对象。我更喜欢使用3D坐标交叉来查看轴的方向。

坐标系

  • 无需更改DefaultUp构造函数中的BoxCustomLayer,因为目标是使所有内容与“三”中的默认坐标对齐。
  • 您可以使用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,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 显示实时调试信息。
  • 点击后,有关匹配的完整信息将记录到控制台。
本文链接:https://www.f2er.com/2983345.html

大家都在问