raycaster 是一条从相机出发、穿过鼠标位置的射线,不能直接用 event.clientx/y 是因浏览器坐标系与 3d 坐标系不重合,需先归一化为裁剪空间(-1 到 1)再通过相机矩阵反算方向。

Raycaster 是什么,为什么不能直接用 event.clientX/Y
Three.js 的 Raycaster 不是“点击检测器”,它本质是一条从相机出发、穿过鼠标位置的射线。浏览器坐标系(event.clientX/event.clientY)和 3D 场景坐标系完全不重合,直接拿屏幕像素去查模型会永远返回空。必须把鼠标位置标准化为裁剪空间坐标(-1 到 1),再结合相机投影矩阵反向计算出射线方向。
标准拾取流程:从 mousemove 到 intersectObjects
核心步骤缺一不可,漏掉任意一步都会导致 raycaster.intersectObjects() 返回空数组:
- 监听
canvas(不是window)上的mousedown或click事件,确保坐标相对于渲染容器 - 将鼠标位置转换为归一化设备坐标:
mouse.x = (event.clientX - rect.left) / rect.width * 2 - 1,mouse.y = -(event.clientY - rect.top) / rect.height * 2 + 1(注意 Y 轴翻转) - 调用
raycaster.setFromCamera(mouse, camera),内部自动处理透视/正交投影差异 - 传入待检测对象数组:
raycaster.intersectObjects([mesh1, mesh2]);若检测整个场景,用scene.children.filter(...)筛出可交互的Mesh
常见失效原因:camera、mesh、raycaster 三者不匹配
最常踩的坑不是代码写错,而是上下文没对齐:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
camera必须是当前渲染所用的相机实例——如果用了OrbitControls,别误传controls.object当作相机 - 被检测的
Mesh必须有几何体(geometry)和材质(material),且material.visible === true、mesh.visible === true、mesh.castShadow === true不影响拾取,但mesh.frustumCulled = false可能导致远距离对象被剔除而无法命中 -
Raycaster实例可以复用,但每次调用setFromCamera()前必须确保mouse向量已更新,否则会沿旧方向发射
性能与精度注意事项
高频交互(如拖拽)下容易出问题:
- 避免在
render循环里每帧都调用intersectObjects()——只在点击瞬间执行即可 - 若模型顶点极多(如数万面的 glTF),考虑用
raycaster.params.MeshBasicMaterial.threshold = 0.1提升粗筛速度(仅对带alphaTest的材质有效) - 拾取结果按距离排序,
intersects[0]是最近命中点;但若需精确到面片(face),得检查intersects[0].face是否存在,且mesh.geometry.attributes.normal必须已计算(geometry.computeVertexNormals())
真正麻烦的是动态更新的网格——比如用 BufferGeometry 批量移动顶点后,不调用 geometry.verticesNeedUpdate = true(旧版)或 geometry.attributes.position.needsUpdate = true(r125+),Raycaster 仍按旧顶点位置计算,结果必然偏移。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










