Three.js光線檢測(cè)

(二)Three.js光線檢測(cè)

摘要:使用three.js中的光線檢測(cè) Raycaster() ,實(shí)現(xiàn)一下效果:

  • 通過點(diǎn)擊處的坐標(biāo),修改攝像機(jī)位置,實(shí)現(xiàn)攝像機(jī)由遠(yuǎn)及近的過渡動(dòng)態(tài)效果(由遠(yuǎn)景到近景)

1、鼠標(biāo)點(diǎn)擊—攝像機(jī)過渡動(dòng)畫

1.1 THREE.Raycaster對(duì)象

官網(wǎng):Raycaster – three.js docs (threejs.org)

因?yàn)槭褂檬髽?biāo)對(duì)模型點(diǎn)擊獲取,那么,再three中可以使用Raycaster()光線檢測(cè)來實(shí)現(xiàn)。再three官網(wǎng)上對(duì)Raycaster的解釋為“此類旨在協(xié)助進(jìn)行光線投射。Raycasting用于鼠標(biāo)拾?。ㄓ?jì)算鼠標(biāo)在3D空間中的哪些對(duì)象)等”,其原理便是

THREE.Raycaster對(duì)象從屏幕上的點(diǎn)擊位置向場(chǎng)景中發(fā)射一束光線,
與攝像機(jī)的位置形成一條光線,在這條光線路徑上的物體,都會(huì)被檢測(cè)到

1.1.1參數(shù)

Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float )
origin —— 光線投射的原點(diǎn)向量。
direction —— 向射線提供方向的方向向量,應(yīng)當(dāng)被標(biāo)準(zhǔn)化。
near —— 返回的所有結(jié)果比near遠(yuǎn)。near不能為負(fù)值,其默認(rèn)值為0。
far —— 返回的所有結(jié)果都比far近。far不能小于near,其默認(rèn)值為Infinity(正無窮。)

1.2 使用到的方法

1.2.1 setFromCamera()

這個(gè)方法中有兩個(gè)變量,

第一個(gè)是在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)中鼠標(biāo)的二維坐標(biāo) —— X分量與Y分量應(yīng)當(dāng)在-1到1之間;

第二個(gè)是場(chǎng)景攝像機(jī)

.setFromCamera ( coords : Vector2, camera : Camera ) : undefined
coords —— 在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)中鼠標(biāo)的二維坐標(biāo) —— X分量與Y分量應(yīng)當(dāng)在-1到1之間。
camera —— 射線所來源的攝像機(jī)。

1.2.2intersectObject()

這個(gè)方法是用來檢測(cè)與射線相交的物體,返回值是一個(gè)Array數(shù)組;

檢測(cè)所有在射線與物體之間,包括或不包括后代的相交部分。返回結(jié)果時(shí),相交部分將按距離進(jìn)行排序,最近的位于第一個(gè)。

.intersectObject ( object : Object3D, recursive : Boolean, optionalTarget : Array ) : Array
object —— 檢查與射線相交的物體。
recursive —— 若為true,則同時(shí)也會(huì)檢查所有的后代。否則將只會(huì)檢查對(duì)象本身。默認(rèn)值為true。
optionalTarget — (可選)設(shè)置結(jié)果的目標(biāo)數(shù)組。如果不設(shè)置這個(gè)值,則一個(gè)新的Array會(huì)被實(shí)例化;如果設(shè)置了這個(gè)值,
                則在每次調(diào)用之前必須清空這個(gè)數(shù)組(例如:array.length = 0;)。

返回的數(shù)組如下所示:

image-20221028105133108.png

參數(shù)解釋:

distance —— 射線投射原點(diǎn)和相交部分之間的距離。
point —— 相交部分的點(diǎn)(世界坐標(biāo))
face —— 相交的面
faceIndex —— 相交的面的索引
object —— 相交的物體
uv —— 相交部分的點(diǎn)的UV坐標(biāo)。
uv2 —— Second set of U,V coordinates at point of intersection
instanceId – The index number of the instance where the ray intersects the InstancedMesh

當(dāng)計(jì)算這條射線是否和物體相交的時(shí)候,Raycaster將傳入的對(duì)象委托給raycast方法。
這將可以讓mesh對(duì)于光線投射的響應(yīng)不同于lines和pointclouds。

請(qǐng)注意:對(duì)于網(wǎng)格來說,面必須朝向射線的原點(diǎn),以便其能夠被檢測(cè)到。 用于交互的射線穿過面的背側(cè)時(shí),將不會(huì)被檢測(cè)到。
如果需要對(duì)物體中面的兩側(cè)進(jìn)行光線投射, 你需要將material中的side屬性設(shè)置為THREE.DoubleSide。

1.2.3 intersectObjects()

這個(gè)方法和上面的方法相差不多,本方法是用來檢測(cè)一組物體;

檢測(cè)所有在射線與這些物體之間,包括或不包括后代的相交部分。返回結(jié)果時(shí),相交部分將按距離進(jìn)行排序,最近的位于第一個(gè)),相交部分和.intersectObject所返回的格式是相同的。

.intersectObjects ( objects : Array, recursive : Boolean, optionalTarget : Array ) : Array
objects —— 檢測(cè)和射線相交的一組物體。
recursive —— 若為true,則同時(shí)也會(huì)檢測(cè)所有物體的后代。否則將只會(huì)檢測(cè)對(duì)象本身的相交部分。默認(rèn)值為true。
optionalTarget —— (可選)設(shè)置結(jié)果的目標(biāo)數(shù)組。如果不設(shè)置這個(gè)值,則一個(gè)新的Array會(huì)被實(shí)例化;如果設(shè)置了這個(gè)值,
則在每次調(diào)用之前必須清空這個(gè)數(shù)組(例如:array.length = 0;)。

1.3 gsap中的TweenMax動(dòng)畫

中文網(wǎng)址:TweenMax中文手冊(cè)_TweenMax中文網(wǎng)

使用TweenMax動(dòng)畫,控制攝像機(jī)的變換速度,達(dá)到想要的平和效果;

TweenLite.fromTo('div', 5, {opacity:1}, {opacity:0});
//動(dòng)畫目標(biāo):div
//起始狀態(tài):opacity:1
//終點(diǎn)狀態(tài):opacity:0
//補(bǔ)間:5秒完成狀態(tài)改變

1.4主要代碼

1.4.1設(shè)置光線檢測(cè)

//光線檢測(cè),獲取點(diǎn)擊物體的坐標(biāo)值
  rayClick() {
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    const camera = this.camera;
    const scene = this.scene;

    //對(duì)頁(yè)面進(jìn)行鼠標(biāo)點(diǎn)擊事件綁定
    window.addEventListener("mouseup", mouseup);

    //添加點(diǎn)擊方法
    function mouseup(e) {
      // 將鼠標(biāo)位置歸一化為設(shè)備坐標(biāo)。x 和 y 方向的取值范圍是 (-1 to +1)
      mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

      // 通過攝像機(jī)和鼠標(biāo)位置更新射線
      //這里的攝像機(jī)要將外部定義的攝像機(jī)通過新的變量接受到,再次賦值使用,同下方的scene
      //因?yàn)槭髽?biāo)點(diǎn)擊事件的this指的是windows,不是這個(gè)場(chǎng)景Scene,解決辦法
      //可以在 const mouse = new THREE.Vector2(); 后重新賦值一下:const _this = this; 
      //在點(diǎn)擊函數(shù)中就可以使用_this.scene、_this.camera
      raycaster.setFromCamera(mouse, camera);

      // 計(jì)算物體和射線的焦點(diǎn)
      const intersects = raycaster.intersectObjects(scene.children);

      console.log(intersects);
      //選中后進(jìn)行操作
      if (intersects.length) {
        var selected = intersects[0];
        //點(diǎn)擊世界中的物體,改變攝像機(jī)位置到物體前,實(shí)現(xiàn)從遠(yuǎn)景到近景的切換效果
        TweenMax.to(camera.position, 2, {
            x: selected.point.x + 50,
            y: selected.point.y,
            z: selected.point.z + 100,
            ease:Expo.easeInOut,
            onComplete: function (){}
        })
        console.log("x坐標(biāo)" + selected.point.x);
        console.log("y坐標(biāo)" + selected.point.y);
        console.log("z坐標(biāo)" + selected.point.z);
      }
    }
  }

1.5完整代碼

html部分

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three實(shí)現(xiàn)攝像機(jī)動(dòng)畫</title>
    <link rel="stylesheet" href="./assets/css/index.css">
</head>
<body>
    <canvas id="canvasScene"></canvas>
    <script src="./js/index.js" type="module"></script>
</body>
</html>

index.js部分

import Scene from "./Scene";

const canvasEL = document.getElementById('canvasScene');

new Scene(canvasEL);

Scene.js部分

import * as THREE from "three";
//導(dǎo)入鼠標(biāo)控制器控件
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
//導(dǎo)入fbx模型加載器
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
//導(dǎo)入TweenMax動(dòng)畫控件
import { TweenMax } from "gsap/gsap-core";

import { Expo } from "gsap";

export default class Scene {
  canvas;
  scene;
  camera;
  render;
  controls;
  light;

  constructor(el) {
    this.canvas = el;
    this.init();
  }

  init() {
    this.setRender();
    this.setScene();
    this.setCamera();

    this.setControls();
    this.setLight();
    this.animate();
    this.setFbx();
    this.rayClick();
  }

  setScene() {
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(0x002222);
  }
  // 設(shè)置相機(jī)
  setCamera() {
    this.camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      3000
    );
    this.camera.position.set(300, 200, 1000);
    this.camera.aspect = window.innerWidth / window.innerHeight;
    this.camera.updateProjectionMatrix();
    this.scene.add(this.camera);
  }
  // 設(shè)置渲染器
  setRender() {
    this.render = new THREE.WebGL1Renderer({
      canvas: this.canvas,
      //設(shè)置抗鋸齒
      antialias: true,
    });
    //設(shè)置渲染編碼
    this.render.outputEncoding = THREE.sRGBEncoding;
    //設(shè)置渲染寬高
    this.render.setSize(window.innerWidth, window.innerHeight);

    //監(jiān)聽頁(yè)面大小變化,修改器的寬高、攝像機(jī)的比例
    window.addEventListener("resize", () => {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.render.setSize(window.innerWidth, window.innerHeight);
    });
  }
  //設(shè)置控制器
  setControls() {
    this.controls = new OrbitControls(this.camera, this.render.domElement);
  }
  //設(shè)置燈光
  setLight() {
    this.light = new THREE.SpotLight();
    this.light.position.set(100, 500, 300);
    this.scene.add(this.light);
  }
  //設(shè)置渲染函數(shù)
  animate = () => {
    this.render.render(this.scene, this.camera);
    window.requestAnimationFrame(this.animate);
  };
  //添加fbx模型
  setFbx() {
    const fbxLoader = new FBXLoader();
    fbxLoader.load("./model/house.fbx", (house) => {
      const scale = 0.05;
      house.scale.set(scale, scale, scale);
      this.scene.add(house);
      house.position.set(0, 0, 0);
    });
  }

  //光線檢測(cè),獲取點(diǎn)擊物體的坐標(biāo)值
  rayClick() {
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    const camera = this.camera;
    const scene = this.scene;

    //對(duì)頁(yè)面進(jìn)行鼠標(biāo)點(diǎn)擊事件綁定
    window.addEventListener("mouseup", mouseup);

    //添加點(diǎn)擊方法
    function mouseup(e) {
      // 將鼠標(biāo)位置歸一化為設(shè)備坐標(biāo)。x 和 y 方向的取值范圍是 (-1 to +1)
      mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

      // 通過攝像機(jī)和鼠標(biāo)位置更新射線
      //這里的攝像機(jī)要將外部定義的攝像機(jī)通過新的變量接受到,再次賦值使用,同下方的scene
      raycaster.setFromCamera(mouse, camera);

      // 計(jì)算物體和射線的焦點(diǎn)
      const intersects = raycaster.intersectObjects(scene.children);

      console.log(intersects);
      //選中后進(jìn)行操作
      if (intersects.length) {
        var selected = intersects[0];
        
        //點(diǎn)擊世界中的物體,改變攝像機(jī)位置到物體前,實(shí)現(xiàn)從遠(yuǎn)景到近景的切換效果
        TweenMax.to(camera.position, 2, {
            x: selected.point.x + 50,
            y: selected.point.y,
            z: selected.point.z + 100,
            ease:Expo.easeInOut,
            onComplete: function (){}
        })
        console.log("x坐標(biāo)" + selected.point.x);
        console.log("y坐標(biāo)" + selected.point.y);
        console.log("z坐標(biāo)" + selected.point.z);
      }
    }
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Threejs 為什么? webGL太難用,太復(fù)雜! 但是現(xiàn)代瀏覽器都支持 WebGL 這樣我們就不必使用 Fla...
    強(qiáng)某某閱讀 6,467評(píng)論 1 21
  • 學(xué)習(xí)目標(biāo) 熟悉并掌握Three.js中的基本概念,主要包括場(chǎng)景,攝像機(jī),燈光,渲染器,物體對(duì)象,幾何體,材質(zhì),動(dòng)畫...
    田苗苗_7785閱讀 2,077評(píng)論 2 8
  • Three.js是構(gòu)建web3d場(chǎng)景非常流行的框架,利用three.js我們可以更優(yōu)雅地創(chuàng)建出三維場(chǎng)景和三維動(dòng)畫,...
    YoneChen閱讀 10,172評(píng)論 3 13
  • Three.js 1.Three.js 介紹 OpenGL(英語:Open Graphics Library,譯名...
    GuitarHusky閱讀 2,742評(píng)論 0 1
  • 在學(xué)習(xí) Three.js 之前,感性的了解一些基礎(chǔ)的 3D 知識(shí)后,后面的學(xué)習(xí)就不會(huì)那么糾結(jié)了. 3D 場(chǎng)景前置知...
    人話博客閱讀 1,231評(píng)論 0 0

友情鏈接更多精彩內(nèi)容