Raycaster 的用法

4 阅读3分钟

在 Three.js 中,Raycaster 是一个非常重要的工具,用于检测射线与场景中物体的交点。它可以用来实现点击交互、物体选择、碰撞检测等功能。以下是 Raycaster 的核心用法和常见问题的解决办法。


1. Raycaster 的基本用法

(1) 创建 Raycaster 实例

const raycaster = new THREE.Raycaster();
  • Raycaster 对象用于定义一条射线,起始点和方向可以动态设置。

(2) 设置射线的方向

raycaster.setFromCamera(mouse, camera);
  • setFromCamera 方法根据鼠标位置(NDC 坐标)和相机设置射线的方向。
  • 参数:
    • mouse: 鼠标的归一化设备坐标 (Normalized Device Coordinates, NDC),范围为 [-1, 1]
    • camera: 当前使用的相机对象。

(3) 检测射线与物体的交点

const intersects = raycaster.intersectObjects(scene.children);
  • intersectObjects 方法会返回射线与场景中物体的交点数组。
  • 返回值是一个数组,每个元素包含以下信息:
    • object: 相交的物体。
    • point: 相交点的世界坐标。
    • distance: 射线起点到相交点的距离。
    • face: 相交的面(如果物体是网格)。
    • uv: 相交点的 UV 坐标。

(4) 处理交点

intersects.forEach((intersection) => {
  const object = intersection.object;
  if ('material' in object && object.material instanceof THREE.Material) {
    const mesh = object as THREE.Mesh;
    mesh.material.color.set(0xff0000); // 修改颜色
  }
});
  • 遍历交点数组,对相交的物体进行操作。

2. 鼠标事件与 Raycaster 的结合

(1) 获取鼠标位置

  • 使用 event.clientXevent.clientY 获取鼠标点击的屏幕坐标。
  • 将鼠标坐标映射到 NDC 空间:
const rect = canvas.value!.getBoundingClientRect(); // 获取画布的实际位置和尺寸
const mouse = new THREE.Vector2();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; // 映射到 [-1, 1]
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; // 映射到 [-1, 1]

(2) 绑定鼠标事件

  • 在鼠标点击事件中调用 Raycaster 进行检测:
window.addEventListener('mousedown', (event) => {
  const mouse = getMousePosition(event); // 获取鼠标位置
  raycaster.setFromCamera(mouse, camera); // 设置射线
  const intersects = raycaster.intersectObjects(scene.children); // 检测交点
  handleIntersections(intersects); // 处理交点
});

3. Raycaster 的高级用法

(1) 动态更新射线

  • 如果需要实时更新射线(例如鼠标移动时),可以在 mousemove 事件中重复调用 setFromCamera
window.addEventListener('mousemove', (event) => {
  const mouse = getMousePosition(event);
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(scene.children);
  highlightObject(intersects); // 高亮显示相交物体
});

(2) 可视化射线

  • 使用 ArrowHelper 可视化射线的方向:
const origin = new THREE.Vector3(0, 0, 0); // 射线起点
const direction = new THREE.Vector3(0, 0, -1).normalize(); // 射线方向
const length = 10; // 射线长度
const hex = 0xffff00; // 射线颜色

const arrowHelper = new THREE.ArrowHelper(direction, origin, length, hex);
scene.add(arrowHelper);

(3) 过滤特定类型的物体

  • intersectObjects 方法支持过滤特定类型的物体:
const selectableObjects = scene.children.filter(obj => obj.userData.selectable);
const intersects = raycaster.intersectObjects(selectableObjects);

4. 常见问题及解决办法

(1) 鼠标点击未命中物体

  • 可能原因
    1. 鼠标坐标未正确映射到 NDC 空间。
    2. 物体不在射线范围内。
    3. 物体的材质没有启用 raycast
  • 解决办法
    1. 检查鼠标坐标的计算是否正确,确保使用了画布的实际偏移量。
    2. 调试射线方向,使用 ArrowHelper 可视化射线。
    3. 确保物体的材质支持 raycast,例如 MeshBasicMaterialMeshStandardMaterial

(2) 性能问题

  • 可能原因
    1. 场景中有大量物体,导致 intersectObjects 计算耗时。
  • 解决办法
    1. 使用空间分区算法(如 OctreeBVH)优化射线检测。
    2. 限制检测的物体范围,只检测特定层级或分组的物体。

(3) 物体被遮挡无法选中

  • 可能原因
    1. 前方有透明物体阻挡了射线。
  • 解决办法
    1. 忽略透明物体的检测:raycaster.intersectObjects(objects, false)
    2. 手动过滤透明物体:在 intersectObjects 后检查物体的 material.opacity

5. 总结

Raycaster 是 Three.js 中实现交互功能的核心工具,通过它我们可以轻松实现点击选择、碰撞检测等效果。在实际开发中,需要注意以下几点:

  1. 鼠标坐标映射:确保鼠标坐标正确转换为 NDC 空间。
  2. 射线方向可视化:使用 ArrowHelper 调试射线方向。
  3. 性能优化:对于复杂场景,使用空间分区算法提高检测效率。
  4. 物体过滤:根据需求过滤特定类型的物体。

希望这些内容能够帮助您更好地理解和使用 Raycaster!如果有其他疑问,请随时提问。