关于 threejs-core-raycaster
threejs-core-raycaster 是面向 Three.js 开发者的射线检测参考技能,涵盖拾取 API、交点结果与性能实践,帮助实现交互对象选择。
主要功能
- 01构造与配置 Raycaster
- 02从相机坐标生成射线
- 03检测对象并获取交点
- 04使用图层筛选目标
- 05复用结果数组以减少分配
- 06支持 InstancedMesh 拾取
适用场景
- 01实现鼠标点选和对象悬停
- 02筛选场景中的可交互物体
- 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.mdexamples.mdmethods.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
);
属性
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
ray | Ray | -- | 底层 Ray 对象(起点 + 方向) |
near | number | 0 | 最小相交距离 |
far | number | Infinity | 最大相交距离 |
camera | Camera | -- | 对 Sprite 对象进行射线检测时必需 |
layers | Layers | layer 0 | 图层掩码 —— 只检测图层匹配的对象 |
params | object | 见下文 | 各类型的相交阈值 |
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。
性能指南
-
对鼠标移动事件的射线投射进行节流 —— 切勿在每个
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); } -
使用图层 —— 将可交互对象分配到特定图层,并相应设置
raycaster.layers,以完全跳过不可交互的几何体。 -
维护扁平的可选对象数组 —— 不要对
scene使用recursive: true进行射线投射,而是维护单独的selectableObjects[]数组,并传入recursive: false。 -
复用交点数组 —— 传入
optionalTarget,以避免垃圾回收:const intersections = []; raycaster.intersectObjects(selectableObjects, false, intersections); // process results... intersections.length = 0; -
边界球预检查 —— Three.js 会在三角形相交测试前自动检测边界球。确保已调用
geometry.computeBoundingSphere()(首次渲染时会自动调用,但在首次渲染前进行射线投射时,需要手动调用)。 -
限制
near/far—— 如果已知预期的相交距离,请缩小射线范围,以便提前跳过远处的对象。
参考链接
- references/methods.md —— Raycaster API 签名完整列表
- references/examples.md —— 用于拾取、悬停和 InstancedMesh 的可运行代码示例
- references/anti-patterns.md —— 射线投射中应避免的做法