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

已知投影矩阵,如何计算Three.js相机矩阵?

解决Google Maps WebGL API + Three.js高海拔物体射线检测失效问题

问题核心

当前射线检测逻辑在近地面场景正常,但高海拔(400米+)物体无法被选中,根源在于Google Maps投影矩阵对应的NDC空间与自定义射线的Z值不匹配;纯Three.js原生方法依赖相机位置,但Google Maps未开放该参数。


方案1:修正射线生成逻辑(优先尝试)

你之前用0和0.5作为NDC的Z轴值,不符合标准透视投影的NDC范围(标准NDC的Z轴是-1到1,对应近/远平面)。Google Maps的投影矩阵大概率遵循标准NDC规则,修改射线生成代码即可覆盖整个视锥体:

raycast(normalizedScreenPoint: three.Vector2): three.Intersection[] {
  // 用标准NDC近/远平面Z值(-1/1)创建点
  const ndcNear = new three.Vector3(normalizedScreenPoint.x, normalizedScreenPoint.y, -1);
  const ndcFar = new three.Vector3(normalizedScreenPoint.x, normalizedScreenPoint.y, 1);
  
  // 计算投影矩阵的逆矩阵
  this.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert();
  
  // 将NDC坐标转换到相机空间
  const cameraSpaceNear = ndcNear.applyMatrix4(this.projectionMatrixInverse);
  const cameraSpaceFar = ndcFar.applyMatrix4(this.projectionMatrixInverse);
  
  // 生成射线:原点为近平面点,方向为远-近并归一化
  this.raycaster.ray.origin.copy(cameraSpaceNear);
  this.raycaster.ray.direction.subVectors(cameraSpaceFar, cameraSpaceNear).normalize();
  
  // 执行检测并返回结果
  return this.raycaster.intersectObjects(yourSceneObjectList, true);
}

补充说明:高海拔物体处于视锥体的远平面区域,之前的0.5Z值无法覆盖到该范围,导致射线无法命中。


方案2:从投影矩阵推导相机位置

如果必须获取Three.js空间中的相机位置,可通过以下两种方式:

方式1:利用射线原点间接推导

相机在相机空间中的原点是(0,0,0),将该点通过「投影逆矩阵 + 视图逆矩阵」转换到世界空间即可得到相机位置。但Google Maps未开放视图矩阵,可通过以下间接方式获取:

getCameraWorldPosition(): three.Vector3 {
  // 取NDC空间原点(0,0,-1),转换为相机空间点
  const ndcOrigin = new three.Vector3(0, 0, -1);
  const projectionInverse = new three.Matrix4().copy(this.camera.projectionMatrix).invert();
  const cameraSpacePos = ndcOrigin.applyMatrix4(projectionInverse);
  
  // 结合anchor经纬度反推世界空间位置,需自行推导相机高度
  const cameraLatLngAlt = { ...this.anchor, altitude: this.getCameraAltitude() };
  const cameraWorldPos = new three.Vector3().fromArray(transformer.fromLatLngAltitude(cameraLatLngAlt, this.rotation, this.scale));
  
  return cameraWorldPos;
}

方式2:屏幕坐标对比法(无需射线)

绕过射线检测,直接将物体世界坐标转换为屏幕坐标,与点击位置做距离判断,适合高海拔物体:

checkObjectClick(object: three.Object3D, clickScreenPos: {x: number, y: number}, renderer: three.WebGLRenderer): boolean {
  const worldPos = new three.Vector3();
  object.getWorldPosition(worldPos);
  
  // 将世界坐标转换为NDC坐标
  worldPos.project(this.camera);
  
  // 转换为屏幕坐标(适配渲染器容器尺寸)
  const screenX = (worldPos.x + 1) * renderer.domElement.clientWidth / 2;
  const screenY = (-worldPos.y + 1) * renderer.domElement.clientHeight / 2;
  
  // 距离阈值根据物体大小调整(示例为10像素)
  const distance = Math.sqrt(Math.pow(screenX - clickScreenPos.x, 2) + Math.pow(screenY - clickScreenPos.y, 2));
  return distance < 10;
}

额外排查点

如果修正射线逻辑后仍失效,可检查:

  • Google Maps的Z轴方向:部分地图引擎Z轴向下,而Three.js默认Z轴向外,需反转射线的Z分量。
  • 相机的near/far参数:确保与Google Maps投影矩阵的近远平面范围一致,可尝试将near设为0.1,far设为10000。

内容的提问来源于stack exchange,提问作者Dragonsheep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 11:18:24