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

效果演示

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

相关推荐
涔溪39 分钟前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR1 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜1 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss