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

news2024/11/20 12:41:29

光线投射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物体的material
  instance: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物体,还原Material
    resetLastSelected();
  }
}

//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.coloradmin.cn/o/1655054.html

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

相关文章

爬虫爬取必应和百度搜索界面的图片

爬虫爬取必应和百度搜索界面的图片 爬取bing搜索图片界面爬取百度搜索界面图片结果如下 爬取bing搜索图片界面 浏览器驱动下载地址 对应版本即可 浏览器驱动 mad直接用 import os import re from selenium import webdriver from selenium.webdriver import Keys from sel…

Linux-进程管理类命令实训

实训1:进程查看,终止,挂起及暂停等操作 1.使用ps命令显示所有用户的进程 2.在后台使用cat命令。查看进程cat,并杀死进程 3.使用top命令只显示某一用户的进程。 4.执行命令cat,把Ctrlz挂起进程,输入jobs命令…

第3章:车辆横向控制

3.1 车辆横向运动学模型 注:上一篇博客讲解了车辆的纵向运动控制,这一篇博客将要讲解的是车辆的横向运动控制;横向运动控制主要是控制车辆一直保持在期望的轨迹上行驶(即通过打方向盘保证车辆的横向运动方向符合预期)…

数据猎手:使用Java和Apache HttpComponents库下载Facebook图像

引言 在信息驱动的时代,互联网上的数据成为了无可比拟的宝藏。本文旨在探讨如何通过利用Java和Apache HttpComponents库,从全球最大的社交网络平台Facebook上获取图像数据。 作为全球最大的社交网络平台,Facebook聚集了数以亿计的用户&#…

Rocky、Almalinux、CentOS和Ubuntu系统初始化脚本v8版

Rocky、Almalinux、CentOS和Ubuntu系统初始化脚本 Shell脚本源码地址: Gitee:https://gitee.com/raymond9/shell Github:https://github.com/raymond999999/shell可以去上面的Gitee或Github仓库代码拉取脚本。 支持的功能和系统&#xff1a…

网络编程入门之UDP编程

欢迎各位帅哥美女来捧场,本文是介绍UDP网络编程。在这里,你会见到最详细的教程;细致到每一行代码,每一个api的由来和使用它的目的等。 目录 1.UDP相关API 1.1.两个类 1.2.两个类中的方法 2.UDP编程 2.1.大体框架 2.2.内容构…

pdf2htmlEX:pdf 转 html,医学指南精细化处理第一步

pdf2htmlEX:pdf 转 html,医学指南精细化处理第一步 单文件转换多文件转换 代码:https://github.com/coolwanglu/pdf2htmlEX 拉取pdf2htmlEX 的 Docker: docker pull bwits/pdf2htmlex # 拉取 bwits/pdf2htmlex不用进入容器&…

【CTF Web】XCTF GFSJ0480 simple_js Writeup(JS分析+ASCII码)

simple_js 小宁发现了一个网页,但却一直输不对密码。(Flag格式为 Cyberpeace{xxxxxxxxx} ) 解法 查看网页源代码。 function dechiffre(pass_enc){var pass "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65";var tab pass_enc.split(,);var …

[嵌入式系统-71]:RT-Thread-组件:日志管理系统ulog,让运行过程可追溯

目录 ulog 日志 1. ulog 简介 ulog 架构 配置选项 日志级别 日志标签 2. 日志初始化 初始化 去初始化 3. 日志输出 API 4. 日志使用示例 使用示例 在中断 ISR 中使用 同步模式(Synchronous Mode) 异步模式(Asynchronous Mode&…

DEV--C++小游戏(吃星星(0.2))

目录 吃星星(0.2) 简介 分部代码 头文件(增) 命名空间变量(增) 副函数(新,增) 清屏函数 打印地图函数(增) 移动函数 选择颜色&#xff…

java项目之在线课程管理系统源码(springboot+vue+mysql)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的在线课程管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 在线课程管理系统的主要…

WebRTC 采集音视频数据

WebRTC 采集音视频数据 WebRTC 采集音视频数据getUserMedia API 简介浏览器兼容性getUserMedia 接口格式MediaStreamConstraintsMediaTrackConstraints 采集音频数据MediaStream 和 MediaStreamTrack本地视频预览切换摄像头显示参考 WebRTC 采集音视频数据 getUserMedia API 简…

线性表的拓展之广义表

概述 示例 性质 区别 运算

Spring 常用的注入方式有什么?

Spring 是一个非常流行的 Java 开发框架,它提供了多种依赖注入(Dependency Injection)的方式,使得开发者可以轻松地管理应用程序中的组件依赖关系。在 Spring 中,常用的注入方式主要包括构造器注入、Setter 方法注入、…

Jmeter性能测试(四)

一、遇到问题解决思路 1、检查请求头是否正确 2、检查请求参数是否正确 3、检查鉴权信息是否正确 4、检查变量作用域 5、检查数据提取是否正确(正则/json提取器) 二、请求头检查 1、在Http信息头管理器查看 2、注意这里的变量作用域是全局的 三、请求参数检查 1、在查看结…

英语学习笔记6——What make is it?

What make is it? 它是什么牌子的? make n.(产品的)品牌名称    v. 制作 区别:model n.(产品的)型号       n. 模型       n. 模特 make 指的是大的品牌名称, model 是旗下产品…

Git知识点总结

目录 1、版本控制 1.1什么是版本控制 1.2常见的版本控制工具 1.3版本控制分类 2、集中版本控制 SVN 3、分布式版本控制 Git 2、Git与SVN的主要区别 3、软件下载 安装:无脑下一步即可!安装完毕就可以使用了! 4、启动Git 4.1常用的Li…

Bert 实现情感分析任务

BERT Bert (Bidirectional Encoder Representations from Transformers)预训练模型是 Google 2018开源的自然语言模型,主要有以下特点。 像它名字一样,BERT最显著的特点是其能够为文本中的每个标记考虑双向上下文。与传统的基于…

vue+ant-design+formBuiler表单构建器——技能提升——form design——亲测有效

最近看到后端同事在弄一个后台管理系统,额,前端真的是夹缝中生存啊,AI抢饭碗,后端也想干前端的活儿。。。 他用到了表单构建器,具体效果如下: 网上有很多适用于ElementUi和ant-design的form design插件,下…

FX110书籍推荐:如何快速成为一名专业股票投资人?

股票投资领域有一本神作《股票交易入门》,它是股票从业人员的入门必备书籍。 关于股票入门的书籍很多,但这本书涉及的知识面最全、实用性最强。从这本书里,我们可以领略到股票交易世界的跌宕起伏而又波澜壮阔的魅力。本书作者 本书的作者是美…