经纬志
首页 / three.js / 使用示例

射线拾取

六十个站点悬停即高亮,单击落到地面并读出世界坐标,两种拾取方式一键切换。

three.js 0.160.0 · 更新 2026-10-28
源码与运行效果
运行效果

说明

指针位置先换算成归一化设备坐标,再交给 raycaster.setFromCamera 生成一条从相机出发的射线。 站点模式用 intersectObjects 检测锥体, 地面模式则与一个数学平面求交,后者完全没有几何参与,开销更低。

命中后把锥体的自发光颜色改成蓝色并放大,指针移开即还原。 落点用一枚贴地圆环表示,坐标以场景单位显示在工具栏, 保留两位小数便于核对。

设置数值说明
站点数量60黄金角散点,半径从 4 递增
站点几何锥体,半径 0.7、高 2.2共享几何体,材质各自独立
拾取模式站点 / 地面按钮切换,互不干扰
递归检测false只检测列表内的对象本身
地面平面y = 0Plane 法线朝上,常数为 0
相机固定,位于 (0, 26, 34)不接轨道控制,避免拖拽与拾取冲突

坐标换算

function updatePointer(event) {
  const rect = renderer.domElement.getBoundingClientRect();
  pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
}

raycaster.setFromCamera(pointer, camera);

getBoundingClientRect() 返回的是元素在视口中的实际区域, 比 clientWidth 更可靠:画布被 CSS 缩放、页面滚动、 或嵌在 iframe 里时都不会算错。Y 轴的负号是必须的, 设备坐标原点在左下,屏幕坐标原点在左上。

与平面求交

const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
const hit = new THREE.Vector3();

if (raycaster.ray.intersectPlane(groundPlane, hit)) {
  ring.position.set(hit.x, 0.04, hit.z);
}

intersectPlane 返回布尔值,命中时把结果写入传入的向量, 不命中时该向量保持不变。平面常数项 constant 表示 normal · x + constant = 0 的偏移,所以法线朝上、常数为 0 恰好是水平面 y = 0。

多点标记的位置

站点在动画里轻微起伏,拾取使用的是它们的当前世界位置, 因此悬停判定始终与画面一致。如果在动画中改变了对象层级或缩放, 射线检测会在下一次拾取时自动跟上,不需要手动刷新。

性能边界

每次移动都做一次射线检测在这个规模下没有负担。 换成十万级三角面的地形后,逐帧检测会明显占用主线程, 常见做法是先用数学平面算出大致位置,再在局部范围内做精确检测。

拾取结果只有几何信息。命中项给出坐标、面索引与对象引用, 业务属性需要自己挂在 userData 上。 three.js 不是地图引擎,不会把屏幕点击自动换算成经纬度, 这一步必须由项目里的坐标转换完成。