Three.js 快速入门教程【十八】射线拾取模型——鼠标点击屏幕选中模型或物体

请添加图片描述

系列文章目录

Three.js 快速入门教程【一】开启你的 3D Web 开发之旅
Three.js 快速入门教程【二】透视投影相机
Three.js 快速入门教程【三】渲染器
Three.js 快速入门教程【四】三维坐标系
Three.js 快速入门教程【五】动画渲染循环
Three.js 快速入门教程【六】相机控件 OrbitControls
Three.js 快速入门教程【七】常见几何体类型
Three.js 快速入门教程【八】常见材质类型
Three.js 快速入门教程【九】光源类型
Three.js 快速入门教程【十】常见的纹理类型
Three.js 快速入门教程【十一】天空盒的多种实现方式
Three.js 快速入门教程【十二】外部模型加载
Three.js 快速入门教程【十三】外部模型加载后常见的处理操作
Three.js 快速入门教程【十四】使用Stats.js监控渲染帧率和性能优化
Three.js 快速入门教程【十五】交互神器DragControls使用详解,实现对物体或模型拖拽
Three.js 快速入门教程【十六】调试神器 gui.js使用详解,可视化面板控制场景参数提高开发效率
Three.js 快速入门教程【十七】射线拾取模型——射线与射线投射器Raycaster介绍
Three.js 快速入门教程【十八】射线拾取模型——鼠标点击屏幕选中模型或物体


文章目录

  • 系列文章目录
  • 一、前言
  • 二、射线拾取的核心原理回顾
  • 三、屏幕坐标转标准设备坐标
    • 3.1 屏幕坐标
    • 3.2标准设备坐标
    • 3.3 屏幕坐标转标准设备坐标
      • 通过clientX、clientY转换坐标
  • 四、从摄像机位置向三维空间发射一条无限延伸的射线
  • 五、鼠标点击拾取模型(物体)完整代码
  • 六、总结


一、前言

      在上一篇文章介绍了射线和射线投射器Raycaster使用,了解了射线拾取的核心原理。要实现鼠标点击选中模型的功能,还缺失关键的一步——如何将鼠标的2D屏幕坐标转换为三维空间坐标,并以此坐标为原点,相机与该坐标连线方向为射线方向检测这条射线与场景中模型是否相交。本教程将介绍该核心步骤实现。


二、射线拾取的核心原理回顾

  • 将鼠标的2D屏幕坐标转换为三维空间坐标

  • 从摄像机位置向三维空间发射一条无限延伸的射线

  • 检测这条射线与场景中物体的交点

  • 找出最近的碰撞物体进行处理

上篇文章已经介绍了3,4步骤实现相关知识点,本篇着重讲解1,2步骤实现过程


三、屏幕坐标转标准设备坐标

在 Three.js 等图形渲染库里,射线拾取等操作往往需要用到标准设备坐标(NDC,Normalized Device Coordinates)。而我们鼠标点击所获取到的坐标是屏幕坐标,因此需要把屏幕坐标转换为标准设备坐标。

3.1 屏幕坐标

鼠标点击事件回调对象中clientX 、clientY、offsetX、offsetY等表示坐标属性称为屏幕坐标,不同的坐标属性对应不同坐标系,不同的坐标系区别在于相对的坐标原点不同。

clientX 、clientY表示鼠标点击位置相对于浏览器窗口可视区域的坐标。浏览器窗口的左上角是坐标原点 (0, 0),x 轴正方向向右,y 轴正方向向下

dom.addEventListener('click',function(event){console.log(event.clientX,"clientX" );console.log(event.clientY,"clientY" );
})

offsetX、offsetY表示鼠标点击位置相对于点击HTML元素内填充区域的坐标位置,HTML元素左上角为坐标原点,水平向右方向为x轴正方向,竖直向下方向为y轴正方向。

dom.addEventListener('click',function(event){console.log(event.offsetX,"offsetX" );console.log(event.offsetY,"offsetY" );
})

如下图所示:

在这里插入图片描述

当点击的HTML元素距离浏览器窗口上、左为0时,两个坐标系原点重合,clientX 、clientY和offsetX、offsetY值相同。例如three.js开发中,通常将canvas画布尺寸设置为窗口宽高并直接插入body,当点击画布此时2个坐标系重合。

3.2标准设备坐标

Three.js Canvas画布具有一个标准设备坐标系,该坐标系的坐标原点在canvas画布的正中间位置,x轴水平向右,y轴竖直向上.
标准设备坐标的范围是[-1, 1], Canvas画布的左上角坐标是(-1, 1),右上角坐标是(1, 1),左下角坐标是(-1, -1),右下角坐标是(1, -1)。

如下图所示:

在这里插入图片描述

这种归一化的坐标系统使得在不同分辨率和尺寸的屏幕上,能够以统一的方式处理图形和交互操作

3.3 屏幕坐标转标准设备坐标

在这里插入图片描述

假设 画布宽高为width、height,则屏幕坐标转换为标准设备坐标计算公式如下

// 屏幕坐标转标准设备坐标
canvas.addEventListener('click',function(event){const x = (event.offsetX / width) * 2 - 1; //标准设备坐标xconst y = -(event.offsetY / height) * 2 + 1;//标准设备坐标y
})

解释:
event.offsetX / width值范围为0~1, (event.offsetX / width) * 2,值范围为0~2,(event.offsetX / width) * 2 - 1值范围为-1~1跟标准设备坐标范围一致。
同理event.offsetY / height值范围0-1,-(event.offsetY / height) * 2,值范围-2~0,-(event.offsetY / height) * 2+1值范围-1~1,因为y轴方向相反所以取相反数

而画布宽高可以通过如下方式获取:

// 获取画布宽高const width= renderer.domElement.clientWidth;const height= renderer.domElement.clientHeight;

最终为:

//渲染画布
let canvas=renderer.domElement
// 屏幕坐标转标准设备坐标
canvas.addEventListener('click',function(event){const x = (event.offsetX / canvas.clientWidth) * 2 - 1; //标准设备坐标xconst y = -(event.offsetY / canvas.clientHeight) * 2 + 1;//标准设备坐标y
})

通过clientX、clientY转换坐标

上述方式是通过offsetX 、offsetY 进行转换,也可以通过clientX、clientY计算转换
由3.1介绍我们知道了两个坐标系区别,假设渲染画布距离窗口左边距离为left,上边距离为top,能得出:

 const offsetX =clientX-leftconst offsetY =clientY-top

在这里插入图片描述

而left、top可通过DOM元素的边界矩形获取

// 获取渲染器DOM元素的边界矩形const rect = renderer.domElement.getBoundingClientRect();const left=rect.leftconst top=rect.top

最终为:

//渲染画布
let canvas=renderer.domElement
// 屏幕坐标转标准设备坐标
canvas.addEventListener('click',function(event){const rect = canvas.getBoundingClientRect();const left=rect.leftconst top=rect.topconst x = ((event.clientX-left) / canvas.clientWidth) * 2 - 1; //标准设备坐标xconst y = -((event.clientY-top) / canvas.clientHeight) * 2 + 1;//标准设备坐标y
})

四、从摄像机位置向三维空间发射一条无限延伸的射线

上一篇文章我们介绍了射线投射器(Raycaster),射线投射器实例还有一个setFromCamera方法未做介绍,它的作用是依据鼠标位置(以标准化设备坐标形式呈现)和相机信息设置射线的原点和方向。

setFromCamera(coords: Vector2, camera: Camera)

下面详细介绍其参数:

  • coords:该参数代表鼠标在标准化设备坐标中的位置,类型为 Vector2。
  • camera:此参数为场景中使用的相机对象,像 THREE.PerspectiveCamera 或 THREE.OrthographicCamera 这类

示例:

// 创建射线投射器
const raycaster = new THREE.Raycaster();
//渲染画布
let canvas=renderer.domElement
// 添加画布点击事件
canvas.addEventListener('click',function(event){
// 创建一个二维向量用于存储标准设备坐标const mouse = new THREE.Vector2();//屏幕坐标转标准设备坐标mouse.x = (event.offsetX / canvas.clientWidth) * 2 - 1; //标准设备坐标xmouse.y = -(event.offsetY / canvas.clientHeight) * 2 + 1;//标准设备坐标y// 通过鼠标位置和相机信息更新射线投射器raycaster.setFromCamera(mouse, camera);
})

最后结合模拾取模型核心原理3,4步骤就能检查鼠标点击位置发出的射线是否与模型有相交从何确定是否选中模型


五、鼠标点击拾取模型(物体)完整代码

完整示例代码——实现点击选中物体,并改变其材质为红色半透明:

import * as THREE from "three";
//引入相机控制器
import { OrbitControls } from "three/addons/controls/OrbitControls.js";// 创建场景
const scene = new THREE.Scene();// 创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,3000
);
camera.position.set(1, 3, 6);// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambientLight);//添加平行光
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(0, 2, 20);
scene.add(light);// 创建立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshLambertMaterial({ color: 0x8844aa });
const box = new THREE.Mesh(geometry, material);
box.position.set(0, 0, 0);
scene.add(box);// //创建圆柱体
const geometry2 = new THREE.CylinderGeometry(0.5, 0.5, 1);
const material2 = new THREE.MeshLambertMaterial({color: 0x0000ff,
});
const cylinder = new THREE.Mesh(geometry2, material2);
cylinder.position.set(-4, 0, 0);
scene.add(cylinder);// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);/**** 拾取模型相关代码*/// 创建射线投射器
const raycaster = new THREE.Raycaster();
// 创建一个二维向量用于存储标准设备坐标
const mouse = new THREE.Vector2();
//渲染画布
let canvas = renderer.domElement;
// 添加画布点击事件
canvas.addEventListener("click", function (event) {//屏幕坐标转标准设备坐标mouse.x = (event.offsetX / canvas.clientWidth) * 2 - 1; //标准设备坐标xmouse.y = -(event.offsetY / canvas.clientHeight) * 2 + 1; //标准设备坐标y// 通过鼠标位置和相机信息更新射线投射器raycaster.setFromCamera(mouse, camera);// 检测射线与立方体、圆柱体相交情况const intersects = raycaster.intersectObjects([box, cylinder]);//有选中处理选中逻辑if (intersects.length > 0) {// 选中的物体设置成红色半透明intersects[0].object.material = new THREE.MeshLambertMaterial({color: 0xff0000,transparent: true,opacity: 0.5,});}
});// 创建 OrbitControls 控件
const controls = new OrbitControls(camera, renderer.domElement);
// 渲染循环
function animate() {requestAnimationFrame(animate);controls.update();renderer.render(scene, camera);
}animate();

运行效果:

在这里插入图片描述


六、总结

       通过本教程,你应该已经了解了射线(Ray)、射线拾取模型(Raycaster)、屏幕坐标转标准设备坐标的相关概念和实现方法,以及相关 API 的使用。射线拾取模型是 Three.js 中非常重要的一个功能,它可以为你的 3D 应用程序增添更多的交互性。希望你可以将这些知识应用到实际项目中,创造出更加精彩的 3D 场景。

更多three.js入门知识点请关注该系列教程后续的更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/74654.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Object.defineProperty()Proxy详解(Vue23数据劫持实现)

底层原理👇🏿 总结一下,结构应该包括: 1. 方法的基本作用和参数。 2. 数据描述符和存取描述符的区别。 3. 属性定义的内部处理流程。 4. 在Vue中的应用实例。 5. 常见错误和正确实践。 每个部分都要结合搜索结果的信息&…

MySQL 进阶语法:函数、约束、多表查询、事务

目录 一、MySQL 常用函数 1. 字符串函数 1.1 基本字符串操作 1.2 字符串截取与处理 1.3 字符串搜索与替换 2. 数值函数 2.1 基本数学运算 2.2 数学计算 2.3 随机数与符号 3. 日期时间函数 3.1 获取当前时间 3.2 日期时间计算 3.3 日期时间提取 3.4 日期时间格式化…

第 12 章(番外)| Solidity 安全前沿趋势 × 审计生态 × 职业路径规划

🌐 第 12 章(番外)| Solidity 安全前沿趋势 审计生态 职业路径规划 ——做得了审计,也接得了项目,走进 Web3 安全工程师的职业实战地图 ✅ 本章导读 Solidity 安全,不只是代码安全、业务安全、审计安全…

1、pytest基本用法

目录 先给大家分享下学习资源 1. 安装pytest 2. 编写用例规则 3. 执行用例 最近在学习pytest的用法 并且用这套框架替换了原来的unittest, 同是测试框架 确实感觉到pytest更加便捷 这边分享给大家我得学习心得 先给大家分享下学习资源 1 官方文档 pytest 官方…

【sylar-webserver】5 协程调度模块

文章目录 设计思路三种协程的切换 协程调度模块,需要把前面的线程模块和协程模块结合使用 ~ 设计思路 构造函数定义 线程池 基本信息。start(),创建线程池,每个线程创建都执行 run()。每个线程在 run() 里,查找任务队列 m_tasks…

Go 语言规范学习(1)

文章目录 IntroductionNotation示例(Go 语言的 if 语句): Source code representationCharacters例子:变量名可以是中文 Letters and digits Lexical elementsCommentsTokensSemicolons例子:查看程序所有的token Ident…

探索抓包利器ProxyPin,实现手机APP请求抓包,支持https请求

以下是ProxyPin的简单介绍: - ProxyPin是一个开源免费HTTP(S)流量捕获神器,支持 Windows、Mac、Android、IOS、Linux 全平台系统- 可以使用它来拦截、检查并重写HTTP(S)流量,支持捕获各种应用的网络请求。ProxyPin基于Flutter开发&#xff0…

深度学习3-pytorch学习

深度学习3-pytorch学习 Tensor 定义与 PyTorch 操作 1. Tensor 定义: Tensor 是 PyTorch 中的数据结构,类似于 NumPy 数组。可以通过不同方式创建 tensor 对象: import torch# 定义一个 1D Tensor x1 torch.Tensor([3, 4])# 定义一个 Fl…

深入浅出Spring-Boot-3.x.pdf

通过网盘分享的文件:深入浅出Spring-Boot-3.x.pdf 链接: https://pan.baidu.com/s/10ZkhmeIXphEwND9Rv4EBlg?pwduatm 提取码: uatm

springboot启动事件CommandLineRunner使用

什么是CommandRunner CommandRunner是springboot启动完成时会调用的一个runner 启动参数会传递到这个runner 我们能用来做一些初始化工作和缓存预热等工作 ApplicationRunner VS CommandRunner? 这两个Runner作用一样 只是得到的启动参数格式不一样 前者是一个Argument对象…

数据可视化TensorboardX和tensorBoard安装及使用

tensorBoard 和TensorboardX 安装及使用指南 tensorBoard 和 TensorBoardX 是用于可视化机器学习实验和模型训练过程的工具。TensorBoard 是 TensorFlow 官方提供的可视化工具,而 TensorBoardX 是其社区驱动的替代品,支持 PyTorch 等其他框架。以下是它…

蓝桥杯C++基础算法-多重背包

这段代码实现了一个多重背包问题的动态规划解法。多重背包问题与完全背包问题类似,但每个物品有其数量限制。以下是代码的详细思路解析: 1. 问题背景 给定 n 个物品,每个物品有其体积 v[i]、价值 w[i] 和数量 s[i],以及一个容量为…

【SUNO】【AI作词】【提示词】

仿写歌词提示词模板(升级版) 一、仿写目标 风格定位 音乐风格: [填写目标风格,如:民谣/流行/古风/电子/爵士等]参考案例:如《成都》的叙事民谣,《孤勇者》的励志流行。 情感基调: […

26考研——树与二叉树_树与二叉树的应用(5)

408答疑 文章目录 三、树与二叉树的应用哈夫曼树和哈夫曼编码哈夫曼树的定义概念带权路径长度(WPL)计算示例分析 哈夫曼树的构造算法描述哈夫曼树的性质示例 哈夫曼编码Huffman树的编码规则Huffman树的构建过程前缀编码前缀编码的分析及应用 Huffman树的…

【VUE】day06 动态组件 插槽 自定义指令 ESlint

【VUE】day06 动态组件 & 插槽 & 自定义指令 1. 动态组件1.1 通过不同的按钮展示不同的组件1.1.1回顾click 1.2 keep-alive的使用1.3 keep-alive对应的生命周期函数1.3.1 keep-alive的include属性1.3.2 exclude 1.4 组件注册名称和组件声明时name的区别1.4.1 组件声明时…

nodejs-原型污染链

还是老规矩,边写边学,先分享两篇文章 深入理解 JavaScript Prototype 污染攻击 | 离别歌 《JavaScript百炼成仙》 全书知识点整理-CSDN博客 Ctfshow web入门 nodejs篇 web334-web344_web334 ctfshow-CSDN博客 334-js审计 var express require(expr…

Oracle 数据库通过exp/imp工具迁移指定数据表

项目需求:从prod数据库迁移和复制2个表(BANK_STATE,HBS)的数据到uat数据库环境。 数据库版本:Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 迁移工具:客户端exp/imp工具 -- 执行命令 从Prod数据库导出数据exp us…

企业级基于SpringBoot的MQTT的构建和使用

基于SpringBoot的MQTT配置及使用 首先要使用EMQX搭建一个MQTT服务器&#xff0c;参考文档&#xff1a;EMQX快速开始 本着开源分享的观点&#xff0c;闲话不多说&#xff0c;直接上代码 导入Maven <dependency><groupId>org.springframework.integration</gro…

26考研——图_图的代码实操(6)

408答疑 文章目录 五、图的代码实操图的存储邻接矩阵结构定义初始化插入顶点获取顶点位置在顶点 v1 和 v2 之间插入边获取第一个邻接顶点获取下一个邻接顶点显示图 邻接表结构定义初始化图插入顶点获取顶点位置在顶点 v1 和 v2 之间插入边获取第一个邻接顶点获取下一个邻接顶点…

开源webmail邮箱客户端rainloop的分支版本SnappyMail 设置发件人允许多重身份

RainLoop已多年未更新&#xff0c;SnappyMail 是 RainLoop 的分支&#xff0c;由社区维护。SnappyMail 不仅修复了漏洞&#xff0c;还增加了更多功能和优化。对 IMAP 支持更好&#xff0c;移动端体验也比 RainLoop 更细致。 安装过程和设置跟RainLoop一样&#xff1a; 以宝塔面…