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

效果演示

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

相关推荐
Aolith1 分钟前
React 路由守卫:我用一个组件替代了 Vue 的 beforeEach
前端·react.js
Daybreak5 分钟前
从 PDD、DDD、SDD 到 TDD:我是如何用一套 Agent 工程方法论推进 My-Notion 的
前端
HjhIron32 分钟前
从零实现一个待办事项应用:前端必学的Ajax与Node.js实战
前端·后端
yingyima35 分钟前
JavaScript 正则表达式:从零开始的实战对比
前端
Sammyyyyy1 小时前
月之暗面 Kimi Code 0.4.0 发布,终端 AI 编码助手全面采用 TypeScript,实现毫秒级启动
前端·javascript·人工智能·ai·typescript·servbay
范什么特西1 小时前
配置文件xml和properties
xml·前端
jnene1 小时前
html 时间、价格筛选样式处理
前端·css·html
slongzhang_1 小时前
jquery 修复怪异模式html未声明“<!DOCTYPE html>”
前端·html·jquery
宋拾壹1 小时前
fastadmin列表中查看列表,并且添加增加相应的数据
javascript·php·fastadmin
云水一下2 小时前
Vue.js从零到精通系列(三):组件化基础——Props、Emits、插槽与生命周期
前端·javascript·vue.js