mcpField
T
threejs-core-raycaster

threejs-core-raycaster

github.com/impertio-studio开发23136 次安装30 周前更新
代码仓库

关于 threejs-core-raycaster

threejs-core-raycaster 是面向 Three.js 开发者的射线检测参考技能,涵盖拾取 API、交点结果与性能实践,帮助实现交互对象选择。

主要功能

  1. 01构造与配置 Raycaster
  2. 02从相机坐标生成射线
  3. 03检测对象并获取交点
  4. 04使用图层筛选目标
  5. 05复用结果数组以减少分配
  6. 06支持 InstancedMesh 拾取

适用场景

  1. 01实现鼠标点选和对象悬停
  2. 02筛选场景中的可交互物体
  3. 03识别被命中的实例及其索引

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/impertio-studio/three.js-claude-skill-package --skill threejs-core-raycaster

安装后在 Claude Code 中输入/threejs-core-raycaster确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-patterns.md
examples.md
methods.md

详细介绍

threejs-core-raycaster

快速参考

Raycaster 构造函数
import * as THREE from 'three';

const raycaster = new THREE.Raycaster(
  origin,     // Vector3 — default: (0, 0, 0)
  direction,  // Vector3 — MUST be normalized, default: (0, 0, -1)
  near,       // number — minimum distance, default: 0
  far         // number — maximum distance, default: Infinity
);
属性
属性类型默认值描述
rayRay--底层 Ray 对象(起点 + 方向)
nearnumber0最小相交距离
farnumberInfinity最大相交距离
cameraCamera--对 Sprite 对象进行射线检测时必需
layersLayerslayer 0图层掩码 —— 只检测图层匹配的对象
paramsobject见下文各类型的相交阈值
Params 默认值
raycaster.params = {
  Mesh: {},
  Line: { threshold: 1 },    // world units distance for line hits
  LOD: {},
  Points: { threshold: 1 },  // world units distance for point hits
  Sprite: {}
};
重要警告

绝不要在每次 mousemove 事件发生时都进行射线检测而不做节流 —— 这会导致复杂场景严重掉帧。务必通过 requestAnimationFrame 将检测节流到渲染循环中,或将频率限制为每秒 30-60 次。

绝不要在目标对象是一个扁平数组时,对大型场景图传入 recursive: true —— 务必维护一个可选对象的扁平数组,并传入 recursive: false,以避免不必要的树遍历。

使用 raycaster.set() 时,绝不要忘记归一化方向向量 —— 未归一化的方向会导致所有相交结果中的距离值不正确。

绝不要每帧都分配新的结果数组 —— 务必通过 optionalTarget 参数复用数组,并在处理后使用 array.length = 0 清空它。

只与一部分对象交互时,绝不要对整个 scene.children 进行射线检测 —— 务必维护单独的可选对象数组,或使用图层进行筛选。

对 Sprite 对象进行射线检测时,绝不要省略 raycaster.camera —— 射线检测器需要摄像机引用来计算精灵的屏幕空间边界。


核心方法

set(origin, direction)

手动设置射线的起点和方向。方向必须归一化。

const origin = new THREE.Vector3(0, 1, 0);
const direction = new THREE.Vector3(0, -1, 0); // already normalized
raycaster.set(origin, direction);
setFromCamera(coords, camera)

根据标准化设备坐标(NDC)和摄像机设置射线。这是标准的鼠标拾取方法。

  • coords:Vector2,x 和 y 的范围为 [-1, +1]
  • camera:PerspectiveCamera 会生成一条从摄像机穿过该点的射线;OrthographicCamera 会生成一条平行射线
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersectObject(object, recursive?, optionalTarget?)

检测单个对象(以及可选的子对象)是否相交。

  • recursive(默认值:true):为 true 时,检测所有子对象
  • optionalTarget:可复用的数组,用于避免分配内存
  • 返回值:Intersection[],始终按距离排序(最近的在前)
intersectObjects(objects, recursive?, optionalTarget?)

检测对象数组中的对象是否相交。

  • recursive(默认值:true):为 true 时,检测每个对象的子对象
  • optionalTarget:可复用的数组,用于避免分配内存
  • 返回值:Intersection[],始终按距离排序(最近的在前)

相交对象格式

返回数组中的每个相交对象都具有以下结构:

interface Intersection {
  distance: number;       // distance from ray origin to hit point
  point: Vector3;         // hit point in world space
  face: Face | null;      // hit face ({a, b, c} vertex indices + normal)
  faceIndex: number;      // index of hit face in the geometry
  object: Object3D;       // the intersected object reference
  uv?: Vector2;           // UV coordinates at intersection point
  uv1?: Vector2;          // second UV set (if available)
  normal?: Vector3;       // interpolated surface normal at hit point
  instanceId?: number;    // instance index (only for InstancedMesh)
}

访问 intersects[0] 前,务必检查 intersects.length > 0。未命中任何对象时,数组为空。


NDC 转换(标准化设备坐标)

使用 setFromCamera 时,需要将 DOM 鼠标坐标转换为 NDC。NDC 空间的范围为 -1(左侧/底部)到 +1(右侧/顶部)。

// For fullscreen canvas (canvas fills entire window)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

// For non-fullscreen canvas (canvas has offset within page)
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

当画布未填满整个视口时,务必使用 getBoundingClientRect()。在画布只占部分视口时使用 window.innerWidth/Height 会产生错误的 NDC 值,并导致拾取位置偏移。


InstancedMesh 拾取

对 InstancedMesh 进行射线检测时,相交结果会包含用于标识命中实例的 instanceId:

import * as THREE from 'three';

const intersects = raycaster.intersectObject(instancedMesh);
if (intersects.length > 0) {
  const instanceId = intersects[0].instanceId;
  // Retrieve the instance's transform matrix
  const matrix = new THREE.Matrix4();
  instancedMesh.getMatrixAt(instanceId, matrix);

  // Example: change the color of the hit instance
  const color = new THREE.Color();
  instancedMesh.getColorAt(instanceId, color);
  instancedMesh.setColorAt(instanceId, new THREE.Color(0xff0000));
  instancedMesh.instanceColor.needsUpdate = true;
}

使用前,务必检查 instanceId !== undefined —— 非 InstancedMesh 对象没有此属性。


图层过滤

Raycaster 遵循 Layers 系统。只有图层与 raycaster.layers 重叠的对象才会进行测试。

// Assign objects to layers
selectableMesh.layers.set(1);        // exclusively on layer 1
decorationMesh.layers.set(2);        // exclusively on layer 2

// Configure raycaster to only test layer 1
raycaster.layers.set(1);
// Now intersectObjects skips all objects NOT on layer 1

// Enable multiple layers
raycaster.layers.enable(1);
raycaster.layers.enable(2);
// Now tests objects on layer 1 OR layer 2

// Reset to default (layer 0 only)
raycaster.layers.set(0);

使用图层创建拾取分组:可交互对象放在图层 1,非交互装饰放在图层 2,辅助对象/操纵控件放在图层 3。


性能指南

  1. 对鼠标移动事件的射线投射进行节流 —— 切勿在每个 mousemove 事件中都进行射线投射。使用一个在渲染循环中检查的标志:

    let needsRaycast = false;
    canvas.addEventListener('pointermove', (event) => {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      needsRaycast = true;
    });
    
    function animate() {
      if (needsRaycast) {
        raycaster.setFromCamera(mouse, camera);
        // perform intersection tests
        needsRaycast = false;
      }
      renderer.render(scene, camera);
      requestAnimationFrame(animate);
    }
    
  2. 使用图层 —— 将可交互对象分配到特定图层,并相应设置 raycaster.layers,以完全跳过不可交互的几何体。

  3. 维护扁平的可选对象数组 —— 不要对 scene 使用 recursive: true 进行射线投射,而是维护单独的 selectableObjects[] 数组,并传入 recursive: false。

  4. 复用交点数组 —— 传入 optionalTarget,以避免垃圾回收:

    const intersections = [];
    raycaster.intersectObjects(selectableObjects, false, intersections);
    // process results...
    intersections.length = 0;
    
  5. 边界球预检查 —— Three.js 会在三角形相交测试前自动检测边界球。确保已调用 geometry.computeBoundingSphere()(首次渲染时会自动调用,但在首次渲染前进行射线投射时,需要手动调用)。

  6. 限制 near/far —— 如果已知预期的相交距离,请缩小射线范围,以便提前跳过远处的对象。


参考链接

官方来源

评论

支持 Markdown