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

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

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

相关文章

SpringCloudAlibaba:4.1云原生网关higress的搭建

概述 简介 Higress是基于阿里内部的Envoy Gateway实践沉淀、以开源Istio Envoy为核心构建的下一代云原生网关, 实现了流量网关 微服务网关 安全网关三合一的高集成能力,深度集成Dubbo、Nacos、Sentinel等微服务技术栈 定位 在虚拟化时期的微服务架构…

【DevOps】Jenkins 集成Docker

目录 1. 安装 Docker 和 Jenkins 2. 在 Jenkins 中安装 Docker 插件 3. 配置 Docker 连接 4. 创建 Jenkins Pipeline 5. 示例 Pipeline 脚本 6. 运行 Jenkins Job 7. 扩展功能 8、docker配置测试连接的时候报错处理 将 Docker 与 Jenkins 集成可以实现持续集成和持续交…

目标检测正负样本区分和平衡

1、正负样本定义 rpn和rcnn的正负样本定义都是基于MaxIoUAssigner,只不过定义阈值不一样而已。 MaxIoUAssigner的操作包括4个步骤: 首先初始化时候假设每个anchor的mask都是-1,表示都是忽略anchor 将每个anchor和所有gt的iou的最大Iou小于…

如何为 Nestjs 编写单元测试和 E2E 测试

前言 最近在给一个 nestjs 项目写单元测试(Unit Testing)和 e2e 测试(End-to-End Testing,端到端测试,简称 e2e 测试),这是我第一次给后端项目写测试,发现和之前给前端项目写测试还…

Rust里的Fn/FnMut/FnOnce和闭包匿名函数关系

闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使…

Linux线程安全,互斥量和条件变量

文章目录 一、 Linux线程互斥1. 进程线程间的互斥相关背景概念(1) 临界资源和临界区(2) 互斥和原子性 2. 互斥量mutex3. 互斥量的接口4. 互斥量实现原理探究 二、 可重入VS线程安全1. 概念2. 常见的线程不安全的情况3. 常见的线程…

【superset】基于MySQL的BI数据分析可视化实战案例(已更新)

1.熟悉、梳理、总结下superset可视化分析实战案例知识体系,一直想探索有效可用的可视化分析方案,大多收费或不好用,这里,借此机会总结、更新下。 2.复杂度高,遇到并解决的问题较多,尝试了很多次。 3.欢迎批评指正,跪谢一键三连! 基于MySQL的BI数据分析可视化实战案例文…

Crossplane 实战:构建统一的云原生控制平面

1 什么是 Crossplane Crossplane 是一个开源的 Kubernetes 扩展,其核心目标是将 Kubernetes 转化为一个通用的控制平面,使其能够管理和编排分布于 Kubernetes 集群内外的各种资源。通过扩展 Kubernetes 的功能,Crossplane 对 Kubernetes 集群…

rv1126的rknn1.7.5自有模型训练部署

几乎一年前, 弄过一次rv1126的平台的推理部署, 一年时间过去了, rknn从1.7.1, 进化到了1.7.5,原有的代码不太好用了, 因为最近有个客户要做1126平台的推理, 今天下午就花了几个小时, 从头再捋了一遍. 模型训练 这部分, 跟3588平台差不多, clone下yolov5的仓库, 并check out到…

《QT实用小工具·五十五》带有标签、下划线的Material Design风格输入框

1、概述 源码放在文章末尾 该项目实现了一个带有标签动画、焦点动画、正确提示、错误警告的单行输入框控件。下面是demo演示&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef LABELEDEDIT_H #define LABELEDEDIT_H#include <QObject> #include <QWidget>…

Day15-JavaWeb开发-Maven高级-分模块设计与开发继承与聚合私服

1. Maven高级-分模块设计与开发 2. Maven高级-继承与聚合 2.1 继承关系实现 2.2 版本锁定 2.3 聚合实现 3. Maven高级-私服 3.1 私服-介绍 3.2 私服-资源上传与下载 4. Web开发-完结

Nginx(参数设置总结)

文章目录 Nginx&#xff08;工作机制&参数设置&#xff09;1.Master&Worker工作机制1.示意图2.解释3.Nginx争抢机制4.accept_mutex解决惊群现象5.多进程结构不用多线程结构的好处6.IO多路复用&#xff0c;实现高并发7.优势 2.参数配置1.work_processes1.基本介绍2.work…

15_Scala面向对象编程_访问权限

文章目录 Scala访问权限1.同类中访问2.同包不同类访问3.不同包访问4.子类权限小结 Scala访问权限 知识点概念 private --同类访问private[包名] --包私有&#xff1b; 同类同包下访问protected --同类&#xff0c;或子类 //同包不能访问(default)(public)默认public --公…

【电子通识】为什么IC内部偏置会用到恒流源?

在查看芯片手册时&#xff0c;我们经常会发现芯片框图中出现恒流源。下图所示LM358运算放大器规格书中功能框图的恒流源&#xff1a; 电源芯片SS端内部的恒流源&#xff1a; 其实&#xff0c;IC内部电路的偏置&#xff0c;大多通过恒流源或者恒压源来提供。这与电源波动影响到…

【微服务】分布式事务(通过Seata解决分布式事务问题)

分布式事务 分布式事务Seata微服务集成SeataXA模式XA模式使用 AT模式AT模式实现 分布式事务 在分布式系统中&#xff0c;如果一个业务需要多个服务合作完成&#xff0c;而且每一个服务都有事务&#xff0c;多个事务必须同时成功或失败&#xff0c;这样的事务就是分布式事务&am…

力扣 647. 回文子串

题目来源&#xff1a;https://leetcode.cn/problems/palindromic-substrings/description/ C题解1&#xff1a;暴力解法。不断地移动窗口&#xff0c;判断是不是回文串。 class Solution { public:int countSubstrings(string s) {int len s.size();int res 0;for(int i 0;…

vscode如何配置python

Visual Studio Code配置Python环境 打开Visual Studio Code&#xff0c;点击主界面最左侧最下的选项&#xff08;extension&#xff09;,在搜索框中输入“python”,点击“Install”进行安装。 如下图所示&#xff1a; 重启或点击“Reload”(重载)后&#xff0c;即可使用&…

Vue工程化开发和脚手架Vue CLI

目录 一、介绍 二、使用步骤 1. 全局安装&#xff08;一次&#xff09; 2.查看Vue版本 3.创建项目架子&#xff08;项目名不能使用中文&#xff09; 4.启动项目 一、介绍 Vue CLI是Vue官方提供的一个全局命令工具。可以帮助我们快速创建一个开发的Vue项目的标准化基础架子…

C++进阶:AVL树

AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但 如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查 找元素相当于在顺序表中搜索元素&#xff0c;效率低下 。因此&#xff0c;两位俄罗斯的数学家 G.M. A delson- V elskii 和 E.M. L andis 在 1962 …

C++进阶 | [2] 多态

摘要&#xff1a;多态的概念&#xff0c;多态的条件&#xff0c;虚函数的重写&#xff0c;抽象类&#xff0c;多态的原理&#xff0c;虚函数与虚函数表&#xff0c;与多态有关的问答题 1. Concept 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就…