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)
}

效果演示

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

相关推荐
庸俗今天不摸鱼17 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX1873018 分钟前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下24 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox35 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞37 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行38 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581039 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周42 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei1 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯