iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Modern Web

Three.js 學習日誌系列 第 20

Day19 - Three.js與滑鼠互動操作(三)

  • 分享至 

  • xImage
  •  

Day19 - Three.js與滑鼠互動操作(三)

這裡是「Three.js學習日誌」的第19篇,這篇的內容是要來講解Raycasting的概念。這系列的文章假設讀者看得懂javascript,並且有Canvas 2D Context的相關知識。

今天我們來講講 ~ Three.js中用來偵測滑鼠懸停(Hover) / 點擊(Click) 的技巧: Raycasting(光線投射)

什麼是Raycasting(光線投射)?

當我們在Google上面搜索Raycasting這個詞的時候,其實可以找到兩種方面的應用。

一種是關於3D圖像體積繪製的演算方法,另外一種則是遊戲中對於3D物件的命中檢測/點擊偵測,...等。

其實兩者背後的原理差不多,只是應用的面向不同而已。

3D圖像體積繪製演算 命中檢測/點擊偵測
img img

而我們今天要講的內容跟後者比較有關。

如果要簡單解釋Raycasting這種技術在點擊偵測上的應用原理,我們可以想像當我們點擊螢幕的時候,螢幕會在滑鼠點擊的位置,沿著螢幕的法向量(也就是攝影機的朝向),去延伸出去一條無限長的射線

而若這條射線與任何一個3D物體相交的話,該物體就會被判定為「被點擊」。

img

Three.js 中的Raycasting實作

Three.js中,如果想要做到mesh懸停/點擊偵測,那就要使用Raycaster

import {raycaster} from "https://cdn.skypack.dev/three";

const raycaster = new Raycaster();

// 從相機的朝向發出射線
// pointer是滑鼠映射再畫布上的位置,x和y的值都必須要用-1~+1之間的值來表示
raycaster.setFromCamera( pointer, camera );
// 把一整個scene的mesh通通檢測過一遍,看有沒有相交
const intersects = raycaster.intersectObjects( scene.children );

其實只要弄懂了Raycasting的概念,那這段程式應該也不難理解才對。

除了物件點擊偵測之外,Three.jsRaycaster也可以用來偵測到底是點擊到物件的哪一個,甚至是上的哪一個座標位置

接著我們會用一個簡單的範例來演練如何使用Raycaster

Raycaster 範例演練

1. 從一個空白的Scene開始

老樣子,初始化的過程就跳過了~

首先我們先在這個空白的Scene裡面,填入大量的Box Mesh,然後讓這些Box Mesh以隨機的方式分布。

這邊要特別注意Material一定要By Loop去重新生成,不要讓所有Mesh共用用同一個材質實例。

...
const randomness = 10;//亂度係數
for (let i = 0; i < 300; i++) {
  const geo = new BoxGeometry(1, 1, 1, 10, 10, 10);
  const mat = new MeshStandardMaterial({
      color: new Color("rgba(255,0,0,0.1)"),
      transparent: true
  });
  const mesh = new Mesh(geo, mat);
  mesh.position.set(
    (Math.random() - 0.5) * 2 * randomness, 
    // 之所以要先減去0.5再乘以2,
    //是因為要讓方塊落在-randomness到+randomness的範圍
    (Math.random() - 0.5) * 2 * randomness,
    (Math.random() - 0.5) * 2 * randomness
  );
  scene.add(mesh);
}

2. 抓取滑鼠的位置

接著我們需要像上一回一樣,透過addEventListener去抓取滑鼠座標,並把座標以Vector2的形式記錄下來。

const cursor = new Vector2();
// 這裡綁定的事件如果改成click就會變成點擊偵測了~
renderer.domElement.addEventListener("mousemove", (ev) => {
  const rect = renderer.domElement.getBoundingClientRect();
  // 透過把滑鼠的位置除以canvas寬度,來映射到垂直/水平 -1 ~ 1 的區間
  cursor.x = ((ev.clientX - rect.left) / rect.width - 0.5) * 2;
  cursor.y = -((ev.clientY - rect.top) / rect.height - 0.5) * 2;
  // 因為clientY和Three.js的坐標軸方向相反,所以記得要乘以-1
});

3. 在每一個Tick Loop的循環都對所有的Mesh做檢測

我們接著要對所有的方塊做滑鼠懸停的偵測,假如滑鼠移到某個方塊上,那我們就把那個方塊變成綠色的。

const rc = new Raycaster();

const loop = (time) => {
  rc.setFromCamera(cursor, camera);
  const intersects = rc.intersectObjects(scene.children);
  // 這邊我們必須要先刷新一次Scene裡面所有方塊的顏色,把它洗回去初始狀態
  scene.children.forEach((o) => {
    if (o instanceof Mesh) {
      o.material.color.set(0xff0000);
    }
  });
  // 接著再去把有跟射線相交的方塊洗成綠色
  intersects.forEach((o) => {
    o.object.material.color.set(0x00ff00);
  });
  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame((time) => {
    loop(time);
  });
};

loop();

搭拉~

img

codepen連結:點我

小結

我們今天大致上介紹過了Raycasting還有Raycaster的用法~,而且今天也是Three.js與滑鼠互動操作章節的最後一篇了,希望大家可以繼續保持追蹤~


上一篇
Day18 - Three.js與滑鼠互動操作(二)
下一篇
Day20 - 使用Webpack 5打造Three.js的Boilerplate(一)
系列文
Three.js 學習日誌31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言