Three.js 之鼠标选中物体/对象

本篇文章主要介绍在 threejs 创建的 3 维空间中,如何通过鼠标点击,选取某个物体/对象。前期的准备工作中,我已经创建好了如下所示的一个球体和一个立方体,如果你不清楚它们是如何创建出来的,可以移步《vite + ts 渲染一个旋转的立方体》

坐标的转换

因为设置了 canvas 画布的宽高是占满整个浏览器窗口的,所以不妨直接在全局添加一个监听鼠标点击的事件,打印鼠标点击位置的 offsetXoffsetY

javascript 复制代码
// 代码片段 1
addEventListener('click', event => {
  const offsetX = event.offsetX
  const offsetY = event.offsetY
  console.log(offsetX, offsetY)
})

结果如下:

由上动图可知,假设 canvas 画布的宽高为 400px * 300px,则鼠标点击时,触发 click 事件时回传给我们的 eventoffsetXoffsetY,所依据的坐标系统应该如下所示,左上角为 (0, 0) 点;右下角的 offsetX 等于画布的宽度,offsetY 则为画布的高度:

这与如下在 《从画一个点入手》中介绍的 webgl 中的标准设备坐标系并不相同 ------ y 轴方向相反,(0, 0) 点位置不一致,且在标准设备坐标系中,取值范围都是从 -1 到 1 的:

所以我们需要对点击得到的坐标进行转换,比如点击得到的 (0, 0),(200, 150),(400, 300),就要分别转换为(-1, 1),(0, 0),(1, -1),其实就是将两个坐标系中,x 轴与 y 轴上的点,一一做个映射。 转换公式如下:

javascript 复制代码
// 代码片段 1.1
x = (offsetX / width) * 2 - 1
y = -(offsetY / height) * 2 + 1

其中,widthheight 分别是 canvas 画布的宽高,xy 就是转换后的标准设备坐标的分量值。

光线投射(Raycaster)

在 threejs 中,我们使用光线投射(Raycaster),来实现对鼠标经过的 3 维空间中的物体的拾取:

typescript 复制代码
const raycaster = new THREE.Raycaster()

射线(Ray)

Raycaster 类型的实例有个属性为 ray,它是用于光线投射的射线,类型为 Ray,打印查看 ray 属性:

javascript 复制代码
console.log(raycaster.ray)

结果如下:

其 2 个属性的值虽然都是 Vector3 类型的 3 维向量,但是 direction 表示的是射线的方向,origin 表示的是射线的起始点。

射线拥有我从官方文档截取的下图所示的可以检测是否与某类对象相交的一系列方法:

如果相交,则会返回一个值复制到我们提前准备好的 Vector3 类型的变量(target)中;如果不相交,则返回空值 null

ray 可以直接通过 new THREE.Ray() 生成:

javascript 复制代码
const ray = new THREE.Ray()

setFromCamera 方法

我们的目的是通过鼠标点击来拾取物体,所以我们应该让 raycaster.ray 代表的这条射线,自相机发出,射向鼠标所点击的位置,raycaster 有个 setFromCamera 方法,恰能帮我们实现这一目标:

typescript 复制代码
raycaster.setFromCamera(new THREE.Vector2(x, y), camera)

传入的第 1 个参数为标准化设备坐标中鼠标的二维坐标,即代码片段 1.1 中获取的 xy,它们都是在 -1 到 1 之间的值;

第 2 个参数就是射线所来源的摄像机了,传入的是场景里创建的相机 camera

在代码片段 1 的 click 事件监听回调函数内部生成 raycaster 实例并调用如上 setFromCamera 方法,点击球体,执行 console.log(raycaster.ray) 查看生成的射线实例:

此时射线的起始点 origin,就是之前定义的相机位置:

typescript 复制代码
camera.position.set(0, 0, 10)

intersectObjects 方法

raycaster 拥有一个 intersectObjects 方法,可以用来获取与其 ray 属性所表示的射线相交的物体:

typescript 复制代码
const intersects = raycaster.intersectObjects([cube, sphere])

传入的第 1 个参数是个数组,数组里放的就是我们要检测是否与射线相交的物体:cubesphere

方法的返回值 intersects 是一个数组,射线与多少个物体相交,则数组中就会有几个元素,其中第 1 个元素表示第 1 个相交的物体,每个元素中会包含一些信息:

其中 object 属性的值就是相交的物体,我们可以改变该物体的材质的颜色:

typescript 复制代码
if (intersects.length > 0) {
  intersects[0].object.material.color.set(0xFB3F63)
}

效果演示

最终代码及效果演示如下:

相关推荐
前端大卫26 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘42 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare43 分钟前
浅浅看一下设计模式
前端
Lee川1 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端