Three.js中的Raycasting技术:实现3D场景交互事件的Raycaster详解

前言

Web开发中,Three.js是一个极为强大的库,它让开发者能够轻松地在浏览器中创建和展示3D图形。随着3D技术在网页设计、游戏开发、数据可视化等领域的广泛应用,用户与3D场景的交互变得日益重要。而要实现这种交互,一个核心的技术就是光线投射(Raycasting)。通过Three.js提供的Raycaster类,我们可以检测鼠标或触摸事件在3D空间中的对应位置,进而实现点击、悬停等交互效果。本文将深入探讨如何使用Three.jsRaycaster来实现3D场景的交互事件。
光线投射原理及其属性介绍

通过Raycaster拾取模型进行轮廓高亮走这里>>>

什么是Raycasting?

Raycasting是一种计算机图形学技术,用于确定从一个点(通常是观察者的位置或屏幕上的某一点)发射出的光线与场景中物体的交点。在3D应用中,这一技术常用于模拟光照效果、碰撞检测以及用户交互。简单来说,当你在屏幕上点击或触摸时,Three.js会从该点向场景发射一条虚拟的射线,然后检查这条射线与场景中哪些对象相交,从而判断用户点击了哪个对象。
Three.js中的Raycasting技术:实现3D场景交互事件的Raycaster详解
这个类用于进行raycasting(光线投射)。 光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。

Three.js中的Raycaster

Three.js中,Raycaster类是实现这一功能的关键。它允许你创建一个射线,并提供方法来检测这个射线与场景中对象的交点。以下是使用Raycaster的基本步骤:

Raycaster实例解释

new THREE.Raycaster(origin, direction, near, far)

参数说明:

origin - 光线投射的原点,Vector3类型。
direction - 射线的方向,Vector3类型。
near - 投射近点,不能为负值,应该小于far,其默认值为0
far 投射远点,不能小于near,其默认值为无穷大。

射线交叉对象

创建的光线投射对象有一个intersectObject()方法用来获取射线交叉的对象,使用方法如下

const raycaster = new THREE.Raycaster(origin, direction, near, far)
const arr= raycaster.intersectObjects(object, recursive,optionalTarget)

raycaster.intersectObjects()参数

object - 要检查的是否与射线相交的对象,Object3D类型。
recursive - 是否检查所有后代,可选默认为falseBoolean类型。
optionalTarget - 可选参数,放置结果的目标数组。Array类型。若使用这个参数返回检查结果则在每次调用之前必须清空这个数组。

raycaster.intersectObjects()的返回值

Three.js中的Raycasting技术:实现3D场景交互事件的Raycaster详解

distance - 射线投射原点和相交部分之间的距离。
point - 相交部分的坐标。
face - 相交的面。
faceIndex - 相交的面的索引。
object - 相交的物体。
uv - 相交部分的点的UV坐标。

光线投射示例

Three.js中的Raycasting技术:实现3D场景交互事件的Raycaster详解

示例步骤

步骤一、创建射线

const raycaster = new THREE.Raycaster()

步骤二、用一个二维向量保存鼠标点击画布上的位置

const mouse = new THREE.Vector2(1, 1)

步骤三、监听窗口事件,将x,y轴归一化坐标,通过摄像机和鼠标的位置更新色线,计算物体和射线的焦点能不能碰到物体,碰到物体后随机改变射线照射物体的颜色。

window.addEventListener("click",(e)=>{//设置鼠标向量的x,y值,将XY轴归一化,X从-1到1,Y为从-1到1,所以除以2mouse.x = (e.clientX/window.innerWidth)*2-1mouse.y = -(e.clientY/window.innerHeight)*2+1// 通过摄像机和鼠标的位置,更新射线raycaster.setFromCamera(mouse,this.camera)//计算物体和射线的焦点能不能碰到物体const intersects = raycaster.intersectObjects([sphere1,sphere2,sphere3])if(intersects.length>0){intersects[0].object.material.color.set(this.color16())}
})
代码解释:
raycaster.setFromCamera(mouse,this.camera)

每次渲染循环中,你需要更新射线的起点(通常是相机的位置)和方向(通常是基于鼠标坐标计算出的向量):这里,mouse是归一化设备坐标(即范围在(-1, -1)(1, 1)之间的坐标),可以通过监听鼠标或触摸事件并使用THREE.Vector2和renderer.domElement.clientWidth/Height进行转换得到。

const intersects = raycaster.intersectObjects(scene.children, true);

使用raycaster.intersectObjects()方法来检测射线与场景中对象的交点:此方法返回一个数组,包含了所有与射线相交的对象信息。如果数组不为空,说明有对象被选中,你可以根据需要处理这些交点信息。

什么是归一化坐标:归一化坐标,是一个二维坐标,仅有X/Y两个维度,且X和Y的取值范围均为[-1, 1],坐标原点位于three.js所创建的canvas的中心处。 ​
Three.js中的Raycasting技术:实现3D场景交互事件的Raycaster详解
归一化坐标公式:

mouse.x = ((event.clientX - container.getBoundingClientRect().left) / container.getBoundingClientRect().width) * 2 - 1;
mouse.y = - ((event.clientY - container.getBoundingClientRect().top) / container.getBoundingClientRect().height) * 2 + 1;

以上示例完整代码:

<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from  "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
export default {name: 'HomeView',components: {},mounted(){this.init()},data(){return {camera: null,  //相机对象scene: null,  //场景对象renderer: null,  //渲染器对象mesh: null,  //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{//随机生成十六进制颜色color16(){//十六进制颜色随机var r = Math.floor(Math.random()*256);var g = Math.floor(Math.random()*256);var b = Math.floor(Math.random()*256);var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);return color;},init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建渲染器this.renderer = new THREE.WebGLRenderer();//渲染器尺寸this.renderer.setSize( window.innerWidth,  window.innerHeight );    // 创建三个球const sphere1 = new THREE.Mesh(new THREE.SphereGeometry(1,32,32),new THREE.MeshBasicMaterial({color:0x00ff00}))sphere1.position.x = -3this.scene.add(sphere1)const sphere2 = new THREE.Mesh(new THREE.SphereGeometry(1,32,32),new THREE.MeshBasicMaterial({color:0xff0000}))sphere2.position.x = 0this.scene.add(sphere2)const sphere3 = new THREE.Mesh(new THREE.SphereGeometry(1,32,32),new THREE.MeshBasicMaterial({color:0x0000ff}))      sphere3.position.x = 3this.scene.add(sphere3)//创建射线const raycaster = new THREE.Raycaster()//用一个二维向量保存鼠标点击画布上的位置const mouse = new THREE.Vector2(1, 1)   window.addEventListener("click",(e)=>{//设置鼠标向量的x,y值,将XY轴归一化,X从-1到1,Y为从-1到1,所以除以2mouse.x = (e.clientX/window.innerWidth)*2-1mouse.y = -(e.clientY/window.innerHeight)*2+1console.log(mouse.x,mouse.y)// 通过摄像机和鼠标的位置,更新涉嫌raycaster.setFromCamera(mouse,this.camera)//计算物体和射线的焦点能不能碰到物体const intersects = raycaster.intersectObjects([sphere1,sphere2,sphere3])console.log("intersects",intersects)if(intersects.length>0){intersects[0].object.material.color.set(this.color16())}})this.scene.background=new THREE.Color(0x999999)// 设置相机位置this.camera.position.z = 15;   this.camera.position.y =2;  this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器this.controls = new OrbitControls(this.camera,this.renderer.domElement)//添加阻尼带有惯性this.controls.enableDamping = true//设置阻尼系数this.controls.dampingFactor = 0.05//元素中插入canvas对象container.appendChild(this.renderer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGL.getWebGLErrorMessage();document.getElementById( document.body ).appendChild( warning );}},//旋转起来animate() {this.controls.update()requestAnimationFrame( this.animate );// this.mesh.rotation.x += 0.01;// this.mesh.rotation.y += 0.01;this.renderer.render( this.scene, this.camera );}}
}
</script>

总结

通过Three.jsRaycaster,我们能够以直观且高效的方式实现3D场景中的交互事件。无论是简单的点击反馈,还是复杂的拖拽操作,Raycasting都是构建互动式3D体验不可或缺的一部分。掌握这项技术,无疑能极大提升你的3D应用或游戏的用户体验。希望本文能为你开启探索Three.js交互世界的大门,让你的3D项目更加生动有趣。

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

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

相关文章

探索地产营销管理系统业务架构的设计与应用

随着城市化进程的加速和人们对居住环境需求的不断提升&#xff0c;地产行业正迎来前所未有的发展机遇和挑战。在这个背景下&#xff0c;地产营销管理系统作为地产开发企业营销管理的重要工具&#xff0c;扮演着至关重要的角色。本文将深入探讨地产营销管理系统业务架构的设计与…

手机怎么压缩图片?通过三种压缩操作

手机怎么压缩图片&#xff1f;在智能手机日益普及的今天&#xff0c;拍照分享已成为日常生活的一部分。然而&#xff0c;高质量的照片往往占用较大的存储空间&#xff0c;且在网络上传输时速度较慢。那么&#xff0c;如何在手机上压缩图片呢&#xff1f;本文将介绍三种实用的手…

文件无法在当前环境下执行在 x86_64 系统上运行 ARM 可执行文件

目录 遇到的问题是由于"..."文件无法在当前环境下执行。这个错误通常是因为二进制文件的格式不兼容&#xff0c;可能是因为它是为不同的架构编译的。例如&#xff0c;如果二进制文件是为 x86 架构编译的&#xff0c;但你在 ARM 设备上尝试运行它&#xff0c;就会出现…

[数据集][目标检测]道路圆石墩检测数据集VOC+YOLO格式461张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;462 标注数量(xml文件个数)&#xff1a;462 标注数量(txt文件个数)&#xff1a;462 标注类别…

运放应用2 - 同相放大电路

1. 前置知识 同相放大电路存在 负反馈电路 &#xff0c;工作在线性区&#xff0c;可以利用 虚短 概念来分析电路。 注&#xff1a;运放的 虚断 特性是一直存在的&#xff0c;虚短 特性则需要运放工作在 线性区 有关运放的基础知识&#xff0c;可以参考我的另外一篇文章&#x…

LeetCode题练习与总结:杨辉三角--118

一、题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1…

单元测试之CppTest测试框架

目录 1 背景2 设计3 实现4 使用4.1 主函数4.2 测试用例4.2.1 定义4.2.2 实现 4.3 运行 1 背景 前面文章CppTest实战演示中讲述如何使用CppTest库。其主函数如下&#xff1a; int main(int argc, char *argv[]) {Test::Suite mainSuite;Test::TextOutput output(Test::TextOut…

Linux系统安全(用户、密码、grub引导密码、增加终端)

目录 系统安全 用户安全 密码安全 PAM认证 命令的历史 用户切换 命令的执行权限 grub引导密码 增加终端 系统安全 用户安全 命令 说明 chattr i /etc/passwd chattr&#xff1a;为文件添加特殊权限 i&#xff1a;指定文件设为不可修改&#xff0c;只有root用户能为…

【Centos】深度解析:CentOS下安装pip的完整指南

【Centos】深度解析&#xff1a;CentOS下安装pip的完整指南 大家好 我是寸铁&#x1f44a; 总结了一篇【Centos】深度解析&#xff1a;CentOS下安装pip的完整指南✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 方式1(推荐) 下载get-pip.py到本地 sudo wget https://bootstrap.p…

代理记账公司哪家好,深度剖析与选择指南

代理记账&#xff0c;作为企业会计管理和运营的重要环节&#xff0c;已经逐渐被越来越多的企业所重视&#xff0c;在众多的代理记账公司中&#xff0c;如何选择一家专业、高效且值得信赖的代理记账机构呢&#xff1f;以下是一些深度解析和推荐。 公司的规模 规模较大的代理记账…

LeetCode-数学基础开篇

概念 1.实数 2.指数函数 f(x) &#xff08;a&#xff1e;0且a≠1&#xff09;【a: 底数&#xff08;常量&#xff09;&#xff0c;x: 指数&#xff08;变量&#xff09;】 特征&#xff1a;指数函数在x轴没有交点&#xff0c;是光滑的曲线 3.幂函数 f(x) 【x&#xff…

Java基础之回调函数总结(八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

你会用Nginx的第三方模块吗?

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 你使用过Nginx的第三方模块吗&#xff1f;今天咱们来聊聊Nginx的第三方模块。 在深入了解Nginx的高性能与灵活性的过程中&#xff0c;我们不可避免地会接触到第三方模块。 这些模块是对Nginx原生功能的有力扩展&…

SpringBoot+Redis发送短信

SpringBootRedis发送短信 pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId&g…

Python | Leetcode Python题解之第134题加油站

题目&#xff1a; 题解&#xff1a; class Solution:def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:start, cur_res, total_res 0, 0, 0for i in range(len(gas)):cur_res gas[i] - cost[i]total_res gas[i] - cost[i]if cur_res < 0:cur_r…

Java EE-Spring Security配置

Spring Security 基本概念 spring security 的核心功能主要包括&#xff1a; 认证 &#xff08;你是谁&#xff09; 授权 &#xff08;你能干什么&#xff09; 攻击防护 &#xff08;防止伪造身份&#xff09; 其核心就是一组过滤器链&#xff0c;项目启动后将会自动配置。…

可视化数据科学平台在信贷领域应用系列四:决策树策略挖掘

信贷行业的风控策略挖掘是一个综合过程&#xff0c;需要综合考虑风控规则分析结果、效果评估、线上实时监测和业务管理需求等多个方面&#xff0c;以发现和制定有效的信贷风险管理策略。这些策略可能涉及贷款审批标准的调整、贷款利率的制定、贷款额度的设定等&#xff0c;在贷…

c++简略实现共享智能指针Shared_Ptr<T>

重点&#xff1a; 1.引用计数在堆上&#xff08;原本应为原子变量&#xff09; 2.引用计数增加减少需要加锁保证线程安全。 3.内部实现Release函数用于释放资源 4.未实现&#xff0c;增加自定义删除器可以将Release修改为模板函数&#xff0c;传入可调用参数。对于shared_p…

java分布式的ACP是什么

ACP 1、ACP是什么 一致性&#xff08;Consistency&#xff09;&#xff1a;在分布式系统中&#xff0c;当更新操作完成之后&#xff0c;所有节点在同一时间看到的数据是一致的。换句话说&#xff0c;对于任何数据的读取&#xff0c;都会得到最后写入的数据。可用性&#xff0…

工商注册代理记账——打造专业服务的专业机构

在当今竞争激烈的商业环境中&#xff0c;注册和运营一家公司成为了每一个企业家的重要步骤&#xff0c;这并不是一件容易的事&#xff0c;涉及到的不仅是法律法规的学习&#xff0c;还有各种手续的办理、税务筹划等问题&#xff0c;这个时候&#xff0c;就需要专业的工商注册代…