Three.js 快速入门教程 08 - 基于射线投射器的鼠标场景物体交互

在 Web 3D 项目中经常会碰到和鼠标交互相关的需求,例如用户可以通过鼠标来探索和操控场景中的对象,如旋转、放大缩小、拖动等操作,使用户更加沉浸于虚拟的三维环境中;点击一个对象后触发特定的事件、显示对象的详细信息等,增加场景的交互性和实用性;控制场景的导航,比如通过鼠标拖动实现场景的漫游和平移操作,这样用户可以更方便地探索和浏览场景中的对象。

我们之前已经用 Three.js 内置的相机轨道控制器OrbitControls来添加鼠标交互,本篇章将介绍如何手动去实现自己的鼠标场景交互。

射线

在讲解鼠标交互的具体内容前,需要先了解有关 射线 的相关概念。

射线可以看作是一条无限延伸的直线,它有一个起点和一个方向。通过射线的走向,我们可以判断射线是否与场景中的对象相交。它在计算机图形学和游戏开发中非常常见,用于实现鼠标交互、碰撞检测、拾取物体等功能。

射线投射器Raycaster这么一个工具,可以通过指定起点和方向,发射一条射线到场景中。有了它我们就能实现与场景中的对象进行交互的功能。

实现鼠标场景交互

监听鼠标事件

首先绑定click事件监听

renderer.domElement.addEventListener("click", onClick);

function onClick() {
  // ...
}

创建射线投射器

在事件监听函数中,创建射线投射器。即鼠标每次点击场景时,都会根据当时点击的坐标,动态创建射线投射器用于进行相交检测

function onClick(event) {
  // 创建射线投射器
  const raycaster = new THREE.Raycaster();

  // html元素使用的坐标,是以左上角为坐标原点
  const px = event.offsetX;
  const py = event.offsetY;
  // 将html元素坐标px、py转换成WebGL三维空间坐标x、y
  // width height为渲染容器宽高
  const x = (px / width) * 2 - 1;
  const y = -(py / height) * 2 + 1;

  // 通过转换后的坐标来设置射线的位置和方向
  raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);
}

需要注意的是这里有一个坐标系转换的过程

射线相交检测

射线投射器Raycaster提供了intersectObject()方法,用于检测所有在射线与物体之间的相交关系。返回结果时,相交部分将按距离进行排序,最近的位于第一个。

function onClick(event) {
  // 创建射线投射器
  const raycaster = new THREE.Raycaster();

  // html元素使用的坐标,是以左上角为坐标原点
  const px = event.offsetX;
  const py = event.offsetY;
  // 将html元素坐标px、py转换成WebGL三维空间坐标x、y
  // width height为渲染容器宽高
  const x = (px / width) * 2 - 1;
  const y = -(py / height) * 2 + 1;

  // 通过转换后的坐标来设置射线的位置和方向
  raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);

  // 将射线与场景内的全部对象进行相交检测
  const intersects = raycaster.intersectObjects(scene.children);
}

检测返回的结果intersects是一个数组,里面包含所有与射线相交的对象,因此可以这样判断本次点击是否有选中模型

function onClick(event) {
  // 创建射线投射器
  const raycaster = new THREE.Raycaster();

  // html元素使用的坐标,是以左上角为坐标原点
  const px = event.offsetX;
  const py = event.offsetY;
  // 将html元素坐标px、py转换成WebGL三维空间坐标x、y
  // width height为渲染容器宽高
  const x = (px / width) * 2 - 1;
  const y = -(py / height) * 2 + 1;

  // 通过转换后的坐标来设置射线的位置和方向
  raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);

  // 将射线与场景内的全部对象进行相交检测
  const intersects = raycaster.intersectObjects(scene.children);

  if (intersects.length > 0) {
    console.log("选中的模型", intersects);
  } else {
    console.log("本次点击未选中模型");
  }
}

在实际使用中往往只把第一个与射线相交的认为是被选中的那个,由于检测结果intersects是又近到远排序后的数组,因此取出数组的第一项就是被选中的模型

const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
  // 只关注第一个与射线相交的
  console.log("选中的模型", intersects[0]);
} else {
  console.log("本次点击未选中模型");
}

在 Three.js 的场景中有不同类型的对象,除了模型对象之外,其他类型的对象会干扰射线的检测结果。因此可以通过添加过滤器的方式,排除干扰项

const intersects = raycaster.intersectObjects(scene.children);
// 过滤掉非模型对象
const meshIntersects = intersects.filter((item) => item.isMesh);
if (meshIntersects.length > 0) {
  console.log("选中的模型", meshIntersects[0]);
} else {
  console.log("本次点击未选中模型");
}

完整代码如下

function onClick(event) {
  // 创建射线投射器
  const raycaster = new THREE.Raycaster();

  // html元素使用的坐标,是以左上角为坐标原点
  const px = event.offsetX;
  const py = event.offsetY;
  // 将html元素坐标px、py转换成WebGL三维空间坐标x、y
  // width height为渲染容器宽高
  const x = (px / width) * 2 - 1;
  const y = -(py / height) * 2 + 1;

  // 通过转换后的坐标来设置射线的位置和方向
  raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);

  // 进行射线相交检测
  const intersects = raycaster.intersectObjects(scene.children);
  // 过滤掉非模型对象
  const meshIntersects = intersects.filter((item) => item.isMesh);
  if (meshIntersects.length > 0) {
    // 只关注第一个与射线相交的
    console.log("选中的模型", meshIntersects[0]);
  } else {
    console.log("本次点击未选中模型");
  }
}
  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值