ThreeJS:光线投射与3D场景交互

2024-05-05 13:52

本文主要是介绍ThreeJS:光线投射与3D场景交互,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

光线投射Raycaster

        光线投射详细介绍可参考:https://en.wikipedia.org/wiki/Ray_casting,

        ThreeJS中,提供了Raycaster类,用于进行鼠标拾取,即:当三维场景中鼠标移动时,利用光线投射,可计算出鼠标划过了哪些物体。

        ThreeJS官方案例webgl_interactive_cubes演示了光线投射的具体应用,

        通过Raycaster类实例的intersectObjects方法,可以获取到与射线Ray相交的一组物体;

        通过Raycaster类实例的intersectObject方法,可以获取到与Ray射线相交的第一个物体,

        此外,在鼠标点击或移动时,我们是需要不断去更新Ray射线的,这样才能选中正确的3D物体,可以通过setFromCamera方法,来更新射线,

        接着,还有一个要解决的问题,就是这里的标准化设备坐标中的二维坐标,如何进行处理才是符合ThreeJS的接口规范的?

        核心代码如下,

const mousePosition = new THREE.Vector2(); //记录鼠标位置
//TODO:监听鼠标移动事件
function onPointerMove( event ) {mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
document.addEventListener("mousemove",onPointerMove);

注意到:计算mousePosition.y的计算结果,相比mousePosition.x多乘了一个-1,这是因为屏幕坐标系和ThreeJS的XYZ空间直角坐标系,两者的Y轴是相反的。

3D场景鼠标交互案例

       下面来实现一个利用光线投射控制鼠标选中物体高亮显示的案例,主要效果如下:

①鼠标选中物体,高亮显示,

②鼠标离开物体,取消高亮显示,

         完整示例代码如下,

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; //轨道控制器
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js"; //lil-gui调试工具
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; //HDR加载器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; //GLTF加载器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";const gui = new GUI();//TODO:打印版本
console.warn("threejs版本:", THREE.REVISION);
//TODO:创建时钟
const clock = new THREE.Clock();
//TODO:创建场景
const scene = new THREE.Scene();
//TODO:创建透视相机
const camera = new THREE.PerspectiveCamera(45, //视角window.innerWidth / window.innerHeight,0.1, //近平面1000.0 //远平面
);//TODO:创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);//TODO:轨道控制器
const orbitControls = new OrbitControls(camera, renderer.domElement);
//设置阻尼效果
// orbitControls.enableDamping = true;
orbitControls.update();document.body.appendChild(renderer.domElement);//TODO:添加光源
const light = new THREE.PointLight(0xffffff, 1000, 1000);
light.position.set(15, 15, 15);
scene.add(light);scene.background = new THREE.Color(0xbfe3dd);//TODO:添加3个球体
const ball1 = new THREE.SphereGeometry(1);
const ballMaterial = new THREE.MeshBasicMaterial({color:0xffff00});
const ballMesh1 = new THREE.Mesh(ball1,ballMaterial);
ballMesh1.geometry.scale(0.5,0.5,0.5);
ballMesh1.position.set(-1.0,-1.0,-1.0);const ballMesh2 = new THREE.Mesh(ball1,ballMaterial);
ballMesh2.geometry.scale(0.5,0.5,0.5);
ballMesh2.position.set(1.0,-1.0,-1.0);const ballMesh3 = new THREE.Mesh(ball1,ballMaterial);
ballMesh3.geometry.scale(0.5,0.5,0.5);
ballMesh3.position.set(1.0,1.0,-1.0);const ballMesh4 = new THREE.Mesh(ball1,ballMaterial);
ballMesh4.geometry.scale(0.5,0.5,0.5);
ballMesh4.position.set(1.0,1.0,1.0);scene.add(ballMesh1);
scene.add(ballMesh2);
scene.add(ballMesh3);
scene.add(ballMesh4);//TODO:设置相机位置
camera.position.z = 5.0;
camera.position.y = 2.0;
camera.position.x = 2.0;
camera.lookAt(0, 0, 0);//TODO:利用光线投射,使用鼠标选中Mesh
const rayCaster = new THREE.Raycaster();
const mousePosition = new THREE.Vector2(); //记录鼠标位置
const hightLightColor = new THREE.Color(0xff0000);//高亮颜色
const highlightMaterial = new THREE.MeshBasicMaterial({color:hightLightColor});
const lastSelected = {originMaterial:null,//被选中的3D物体的materialinstance:null,//被选中的3D物体
}
//TODO:监听鼠标移动事件
function onPointerMove( event ) {mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
document.addEventListener("mousemove",onPointerMove);//TODO:还原上一次被选中的3D物体
function resetLastSelected(){if(lastSelected.instance && lastSelected.originMaterial){lastSelected.instance.material = lastSelected.originMaterial;lastSelected.instance = null;lastSelected.originMaterial = null;}
}function pick3DObject(){// 通过摄像机和鼠标位置更新射线_[使用一个新的原点和方向来更新射线。]rayCaster.setFromCamera(mousePosition,camera);/*** objects —— 检测和射线相交的一组物体。recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为true。*/const interscets = rayCaster.intersectObjects(scene.children,false);if(interscets.length>0){const interscetObject = interscets[ 0 ].object;console.log("interscets::",interscetObject);//TODO:还原上一次选中的3D物体resetLastSelected();if(interscetObject){//TODO:鼠标选中3D物体,设置高亮效果//记录当前被选中的物体lastSelected.instance = interscetObject;lastSelected.originMaterial = interscetObject.material;//设置高亮interscetObject.material = highlightMaterial;}}else{//TODO:鼠标离开3D物体,还原MaterialresetLastSelected();}
}//TODO:创建坐标辅助器
// const axesHelper = new THREE.AxesHelper(5);
// scene.add(axesHelper);//TODO:渲染函数
function animate() {requestAnimationFrame(animate);//TODO:旋转立方体// mesh.rotation.x += 0.01;// mesh.rotation.y += 0.01;pick3DObject();//TODO:更新轨道控制器orbitControls.update();//TODO:渲染renderer.render(scene, camera);
}
window.onresize = function () {//TODO:重置渲染器宽高比renderer.setSize(window.innerWidth, window.innerHeight);//TODO:重置相机宽高比camera.aspect = window.innerWidth / window.innerHeight;//TODO:更新相机投影矩阵camera.updateProjectionMatrix();
};animate();//TODO:lil-gui添加调试按钮
const myObject = {// myBoolean: true,fullScreenBtnFunction: function () {document.body.requestFullscreen();},exitFullScreenBtnFunction: function () {document.exitFullscreen();},wireframeMode: false,renderMode: 1,colorSpace: 0,
};gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
//TODO:开启/关闭线框模式
gui.add(myObject, "wireframeMode").name("线框模式").onChange(function (value) {console.log(value);mesh.material.wireframe = value;});
// gui.add(myObject, "myString"); // Text Field
// gui.add(myObject, "myNumber"); // Number Field//TODO:控制Mesh网格正反面显示模式
gui.add(myObject, "renderMode", { 双面模式: 0, 正面模式: 1, 背面模式: 2 }).name("三角形正反面显示模式").onChange(function (value) {console.log(value);switch (value) {case 0: {triangleMesh.material.side = THREE.DoubleSide;break;}case 1: {triangleMesh.material.side = THREE.FrontSide;break;}case 2: {triangleMesh.material.side = THREE.BackSide;break;}}});//TODO:控制颜色空间
gui.add(myObject, "colorSpace", {NoColorSpace: 0,SRGBColorSpace: 1,LinearSRGBColorSpace: 2,}).onChange((value) => {console.log("修改颜色空间::", value);switch (value) {case 0: {diffuseMap.colorSpace = THREE.NoColorSpace;break;}case 1: {diffuseMap.colorSpace = THREE.SRGBColorSpace;break;}case 2: {diffuseMap.colorSpace = THREE.LinearSRGBColorSpace;break;}}});

这篇关于ThreeJS:光线投射与3D场景交互的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/961884

相关文章

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Three.js构建一个 3D 商品展示空间完整实战项目

《Three.js构建一个3D商品展示空间完整实战项目》Three.js是一个强大的JavaScript库,专用于在Web浏览器中创建3D图形,:本文主要介绍Three.js构建一个3D商品展... 目录引言项目核心技术1. 项目架构与资源组织2. 多模型切换、交互热点绑定3. 移动端适配与帧率优化4. 可

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

99%的人都选错了! 路由器WiFi双频合一还是分开好的专业解析与适用场景探讨

《99%的人都选错了!路由器WiFi双频合一还是分开好的专业解析与适用场景探讨》关于双频路由器的“双频合一”与“分开使用”两种模式,用户往往存在诸多疑问,本文将从多个维度深入探讨这两种模式的优缺点,... 在如今“没有WiFi就等于与世隔绝”的时代,越来越多家庭、办公室都开始配置双频无线路由器。但你有没有注

深入解析Java NIO在高并发场景下的性能优化实践指南

《深入解析JavaNIO在高并发场景下的性能优化实践指南》随着互联网业务不断演进,对高并发、低延时网络服务的需求日益增长,本文将深入解析JavaNIO在高并发场景下的性能优化方法,希望对大家有所帮助... 目录简介一、技术背景与应用场景二、核心原理深入分析2.1 Selector多路复用2.2 Buffer

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

java如何实现高并发场景下三级缓存的数据一致性

《java如何实现高并发场景下三级缓存的数据一致性》这篇文章主要为大家详细介绍了java如何实现高并发场景下三级缓存的数据一致性,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 下面代码是一个使用Java和Redisson实现的三级缓存服务,主要功能包括:1.缓存结构:本地缓存:使

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分