Three.js 快速入門教程【十八】射線拾取模型——鼠標點擊屏幕選中模型或物體

請添加圖片描述

系列文章目錄

Three.js 快速入門教程【一】開啟你的 3D Web 開發之旅
Three.js 快速入門教程【二】透視投影相機
Three.js 快速入門教程【三】渲染器
Three.js 快速入門教程【四】三維坐標系
Three.js 快速入門教程【五】動畫渲染循環
Three.js 快速入門教程【六】相機控件 OrbitControls
Three.js 快速入門教程【七】常見幾何體類型
Three.js 快速入門教程【八】常見材質類型
Three.js 快速入門教程【九】光源類型
Three.js 快速入門教程【十】常見的紋理類型
Three.js 快速入門教程【十一】天空盒的多種實現方式
Three.js 快速入門教程【十二】外部模型加載
Three.js 快速入門教程【十三】外部模型加載后常見的處理操作
Three.js 快速入門教程【十四】使用Stats.js監控渲染幀率和性能優化
Three.js 快速入門教程【十五】交互神器DragControls使用詳解,實現對物體或模型拖拽
Three.js 快速入門教程【十六】調試神器 gui.js使用詳解,可視化面板控制場景參數提高開發效率
Three.js 快速入門教程【十七】射線拾取模型——射線與射線投射器Raycaster介紹
Three.js 快速入門教程【十八】射線拾取模型——鼠標點擊屏幕選中模型或物體


文章目錄

  • 系列文章目錄
  • 一、前言
  • 二、射線拾取的核心原理回顧
  • 三、屏幕坐標轉標準設備坐標
    • 3.1 屏幕坐標
    • 3.2標準設備坐標
    • 3.3 屏幕坐標轉標準設備坐標
      • 通過clientX、clientY轉換坐標
  • 四、從攝像機位置向三維空間發射一條無限延伸的射線
  • 五、鼠標點擊拾取模型(物體)完整代碼
  • 六、總結


一、前言

??????在上一篇文章介紹了射線和射線投射器Raycaster使用,了解了射線拾取的核心原理。要實現鼠標點擊選中模型的功能,還缺失關鍵的一步——如何將鼠標的2D屏幕坐標轉換為三維空間坐標,并以此坐標為原點,相機與該坐標連線方向為射線方向檢測這條射線與場景中模型是否相交。本教程將介紹該核心步驟實現。


二、射線拾取的核心原理回顧

  • 將鼠標的2D屏幕坐標轉換為三維空間坐標

  • 從攝像機位置向三維空間發射一條無限延伸的射線

  • 檢測這條射線與場景中物體的交點

  • 找出最近的碰撞物體進行處理

上篇文章已經介紹了3,4步驟實現相關知識點,本篇著重講解1,2步驟實現過程


三、屏幕坐標轉標準設備坐標

在 Three.js 等圖形渲染庫里,射線拾取等操作往往需要用到標準設備坐標(NDC,Normalized Device Coordinates)。而我們鼠標點擊所獲取到的坐標是屏幕坐標,因此需要把屏幕坐標轉換為標準設備坐標。

3.1 屏幕坐標

鼠標點擊事件回調對象中clientX 、clientY、offsetX、offsetY等表示坐標屬性稱為屏幕坐標,不同的坐標屬性對應不同坐標系,不同的坐標系區別在于相對的坐標原點不同。

clientX 、clientY表示鼠標點擊位置相對于瀏覽器窗口可視區域的坐標。瀏覽器窗口的左上角是坐標原點 (0, 0),x 軸正方向向右,y 軸正方向向下

dom.addEventListener('click',function(event){console.log(event.clientX,"clientX" );console.log(event.clientY,"clientY" );
})

offsetX、offsetY表示鼠標點擊位置相對于點擊HTML元素內填充區域的坐標位置,HTML元素左上角為坐標原點,水平向右方向為x軸正方向,豎直向下方向為y軸正方向。

dom.addEventListener('click',function(event){console.log(event.offsetX,"offsetX" );console.log(event.offsetY,"offsetY" );
})

如下圖所示:

在這里插入圖片描述

當點擊的HTML元素距離瀏覽器窗口上、左為0時,兩個坐標系原點重合,clientX 、clientY和offsetX、offsetY值相同。例如three.js開發中,通常將canvas畫布尺寸設置為窗口寬高并直接插入body,當點擊畫布此時2個坐標系重合。

3.2標準設備坐標

Three.js Canvas畫布具有一個標準設備坐標系,該坐標系的坐標原點在canvas畫布的正中間位置,x軸水平向右,y軸豎直向上.
標準設備坐標的范圍是[-1, 1], Canvas畫布的左上角坐標是(-1, 1),右上角坐標是(1, 1),左下角坐標是(-1, -1),右下角坐標是(1, -1)。

如下圖所示:

在這里插入圖片描述

這種歸一化的坐標系統使得在不同分辨率和尺寸的屏幕上,能夠以統一的方式處理圖形和交互操作

3.3 屏幕坐標轉標準設備坐標

在這里插入圖片描述

假設 畫布寬高為width、height,則屏幕坐標轉換為標準設備坐標計算公式如下

// 屏幕坐標轉標準設備坐標
canvas.addEventListener('click',function(event){const x = (event.offsetX / width) * 2 - 1; //標準設備坐標xconst y = -(event.offsetY / height) * 2 + 1;//標準設備坐標y
})

解釋:
event.offsetX / width值范圍為0~1, (event.offsetX / width) * 2,值范圍為0~2,(event.offsetX / width) * 2 - 1值范圍為-1~1跟標準設備坐標范圍一致。
同理event.offsetY / height值范圍0-1,-(event.offsetY / height) * 2,值范圍-2~0,-(event.offsetY / height) * 2+1值范圍-1~1,因為y軸方向相反所以取相反數

而畫布寬高可以通過如下方式獲取:

// 獲取畫布寬高const width= renderer.domElement.clientWidth;const height= renderer.domElement.clientHeight;

最終為:

//渲染畫布
let canvas=renderer.domElement
// 屏幕坐標轉標準設備坐標
canvas.addEventListener('click',function(event){const x = (event.offsetX / canvas.clientWidth) * 2 - 1; //標準設備坐標xconst y = -(event.offsetY / canvas.clientHeight) * 2 + 1;//標準設備坐標y
})

通過clientX、clientY轉換坐標

上述方式是通過offsetX 、offsetY 進行轉換,也可以通過clientX、clientY計算轉換
由3.1介紹我們知道了兩個坐標系區別,假設渲染畫布距離窗口左邊距離為left,上邊距離為top,能得出:

 const offsetX =clientX-leftconst offsetY =clientY-top

在這里插入圖片描述

而left、top可通過DOM元素的邊界矩形獲取

// 獲取渲染器DOM元素的邊界矩形const rect = renderer.domElement.getBoundingClientRect();const left=rect.leftconst top=rect.top

最終為:

//渲染畫布
let canvas=renderer.domElement
// 屏幕坐標轉標準設備坐標
canvas.addEventListener('click',function(event){const rect = canvas.getBoundingClientRect();const left=rect.leftconst top=rect.topconst x = ((event.clientX-left) / canvas.clientWidth) * 2 - 1; //標準設備坐標xconst y = -((event.clientY-top) / canvas.clientHeight) * 2 + 1;//標準設備坐標y
})

四、從攝像機位置向三維空間發射一條無限延伸的射線

上一篇文章我們介紹了射線投射器(Raycaster),射線投射器實例還有一個setFromCamera方法未做介紹,它的作用是依據鼠標位置(以標準化設備坐標形式呈現)和相機信息設置射線的原點和方向。

setFromCamera(coords: Vector2, camera: Camera)

下面詳細介紹其參數:

  • coords:該參數代表鼠標在標準化設備坐標中的位置,類型為 Vector2。
  • camera:此參數為場景中使用的相機對象,像 THREE.PerspectiveCamera 或 THREE.OrthographicCamera 這類

示例:

// 創建射線投射器
const raycaster = new THREE.Raycaster();
//渲染畫布
let canvas=renderer.domElement
// 添加畫布點擊事件
canvas.addEventListener('click',function(event){
// 創建一個二維向量用于存儲標準設備坐標const mouse = new THREE.Vector2();//屏幕坐標轉標準設備坐標mouse.x = (event.offsetX / canvas.clientWidth) * 2 - 1; //標準設備坐標xmouse.y = -(event.offsetY / canvas.clientHeight) * 2 + 1;//標準設備坐標y// 通過鼠標位置和相機信息更新射線投射器raycaster.setFromCamera(mouse, camera);
})

最后結合模拾取模型核心原理3,4步驟就能檢查鼠標點擊位置發出的射線是否與模型有相交從何確定是否選中模型


五、鼠標點擊拾取模型(物體)完整代碼

完整示例代碼——實現點擊選中物體,并改變其材質為紅色半透明:

import * as THREE from "three";
//引入相機控制器
import { OrbitControls } from "three/addons/controls/OrbitControls.js";// 創建場景
const scene = new THREE.Scene();// 創建相機
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,3000
);
camera.position.set(1, 3, 6);// 添加環境光
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambientLight);//添加平行光
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(0, 2, 20);
scene.add(light);// 創建立方體
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshLambertMaterial({ color: 0x8844aa });
const box = new THREE.Mesh(geometry, material);
box.position.set(0, 0, 0);
scene.add(box);// //創建圓柱體
const geometry2 = new THREE.CylinderGeometry(0.5, 0.5, 1);
const material2 = new THREE.MeshLambertMaterial({color: 0x0000ff,
});
const cylinder = new THREE.Mesh(geometry2, material2);
cylinder.position.set(-4, 0, 0);
scene.add(cylinder);// 創建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);/**** 拾取模型相關代碼*/// 創建射線投射器
const raycaster = new THREE.Raycaster();
// 創建一個二維向量用于存儲標準設備坐標
const mouse = new THREE.Vector2();
//渲染畫布
let canvas = renderer.domElement;
// 添加畫布點擊事件
canvas.addEventListener("click", function (event) {//屏幕坐標轉標準設備坐標mouse.x = (event.offsetX / canvas.clientWidth) * 2 - 1; //標準設備坐標xmouse.y = -(event.offsetY / canvas.clientHeight) * 2 + 1; //標準設備坐標y// 通過鼠標位置和相機信息更新射線投射器raycaster.setFromCamera(mouse, camera);// 檢測射線與立方體、圓柱體相交情況const intersects = raycaster.intersectObjects([box, cylinder]);//有選中處理選中邏輯if (intersects.length > 0) {// 選中的物體設置成紅色半透明intersects[0].object.material = new THREE.MeshLambertMaterial({color: 0xff0000,transparent: true,opacity: 0.5,});}
});// 創建 OrbitControls 控件
const controls = new OrbitControls(camera, renderer.domElement);
// 渲染循環
function animate() {requestAnimationFrame(animate);controls.update();renderer.render(scene, camera);
}animate();

運行效果:

在這里插入圖片描述


六、總結

???????通過本教程,你應該已經了解了射線(Ray)、射線拾取模型(Raycaster)、屏幕坐標轉標準設備坐標的相關概念和實現方法,以及相關 API 的使用。射線拾取模型是 Three.js 中非常重要的一個功能,它可以為你的 3D 應用程序增添更多的交互性。希望你可以將這些知識應用到實際項目中,創造出更加精彩的 3D 場景。

更多three.js入門知識點請關注該系列教程后續的更新。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74654.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74654.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74654.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Object.defineProperty()Proxy詳解(Vue23數據劫持實現)

底層原理👇🏿 總結一下,結構應該包括: 1. 方法的基本作用和參數。 2. 數據描述符和存取描述符的區別。 3. 屬性定義的內部處理流程。 4. 在Vue中的應用實例。 5. 常見錯誤和正確實踐。 每個部分都要結合搜索結果的信息&…

MySQL 進階語法:函數、約束、多表查詢、事務

目錄 一、MySQL 常用函數 1. 字符串函數 1.1 基本字符串操作 1.2 字符串截取與處理 1.3 字符串搜索與替換 2. 數值函數 2.1 基本數學運算 2.2 數學計算 2.3 隨機數與符號 3. 日期時間函數 3.1 獲取當前時間 3.2 日期時間計算 3.3 日期時間提取 3.4 日期時間格式化…

第 12 章(番外)| Solidity 安全前沿趨勢 × 審計生態 × 職業路徑規劃

🌐 第 12 章(番外)| Solidity 安全前沿趨勢 審計生態 職業路徑規劃 ——做得了審計,也接得了項目,走進 Web3 安全工程師的職業實戰地圖 ? 本章導讀 Solidity 安全,不只是代碼安全、業務安全、審計安全…

1、pytest基本用法

目錄 先給大家分享下學習資源 1. 安裝pytest 2. 編寫用例規則 3. 執行用例 最近在學習pytest的用法 并且用這套框架替換了原來的unittest, 同是測試框架 確實感覺到pytest更加便捷 這邊分享給大家我得學習心得 先給大家分享下學習資源 1 官方文檔 pytest 官方…

【sylar-webserver】5 協程調度模塊

文章目錄 設計思路三種協程的切換 協程調度模塊,需要把前面的線程模塊和協程模塊結合使用 ~ 設計思路 構造函數定義 線程池 基本信息。start(),創建線程池,每個線程創建都執行 run()。每個線程在 run() 里,查找任務隊列 m_tasks…

Go 語言規范學習(1)

文章目錄 IntroductionNotation示例(Go 語言的 if 語句): Source code representationCharacters例子:變量名可以是中文 Letters and digits Lexical elementsCommentsTokensSemicolons例子:查看程序所有的token Ident…

探索抓包利器ProxyPin,實現手機APP請求抓包,支持https請求

以下是ProxyPin的簡單介紹: - ProxyPin是一個開源免費HTTP(S)流量捕獲神器,支持 Windows、Mac、Android、IOS、Linux 全平臺系統- 可以使用它來攔截、檢查并重寫HTTP(S)流量,支持捕獲各種應用的網絡請求。ProxyPin基于Flutter開發&#xff0…

深度學習3-pytorch學習

深度學習3-pytorch學習 Tensor 定義與 PyTorch 操作 1. Tensor 定義: Tensor 是 PyTorch 中的數據結構,類似于 NumPy 數組。可以通過不同方式創建 tensor 對象: import torch# 定義一個 1D Tensor x1 torch.Tensor([3, 4])# 定義一個 Fl…

深入淺出Spring-Boot-3.x.pdf

通過網盤分享的文件:深入淺出Spring-Boot-3.x.pdf 鏈接: https://pan.baidu.com/s/10ZkhmeIXphEwND9Rv4EBlg?pwduatm 提取碼: uatm

springboot啟動事件CommandLineRunner使用

什么是CommandRunner CommandRunner是springboot啟動完成時會調用的一個runner 啟動參數會傳遞到這個runner 我們能用來做一些初始化工作和緩存預熱等工作 ApplicationRunner VS CommandRunner? 這兩個Runner作用一樣 只是得到的啟動參數格式不一樣 前者是一個Argument對象…

數據可視化TensorboardX和tensorBoard安裝及使用

tensorBoard 和TensorboardX 安裝及使用指南 tensorBoard 和 TensorBoardX 是用于可視化機器學習實驗和模型訓練過程的工具。TensorBoard 是 TensorFlow 官方提供的可視化工具,而 TensorBoardX 是其社區驅動的替代品,支持 PyTorch 等其他框架。以下是它…

藍橋杯C++基礎算法-多重背包

這段代碼實現了一個多重背包問題的動態規劃解法。多重背包問題與完全背包問題類似,但每個物品有其數量限制。以下是代碼的詳細思路解析: 1. 問題背景 給定 n 個物品,每個物品有其體積 v[i]、價值 w[i] 和數量 s[i],以及一個容量為…

【SUNO】【AI作詞】【提示詞】

仿寫歌詞提示詞模板(升級版) 一、仿寫目標 風格定位 音樂風格: [填寫目標風格,如:民謠/流行/古風/電子/爵士等]參考案例:如《成都》的敘事民謠,《孤勇者》的勵志流行。 情感基調: […

26考研——樹與二叉樹_樹與二叉樹的應用(5)

408答疑 文章目錄 三、樹與二叉樹的應用哈夫曼樹和哈夫曼編碼哈夫曼樹的定義概念帶權路徑長度(WPL)計算示例分析 哈夫曼樹的構造算法描述哈夫曼樹的性質示例 哈夫曼編碼Huffman樹的編碼規則Huffman樹的構建過程前綴編碼前綴編碼的分析及應用 Huffman樹的…

【VUE】day06 動態組件 插槽 自定義指令 ESlint

【VUE】day06 動態組件 & 插槽 & 自定義指令 1. 動態組件1.1 通過不同的按鈕展示不同的組件1.1.1回顧click 1.2 keep-alive的使用1.3 keep-alive對應的生命周期函數1.3.1 keep-alive的include屬性1.3.2 exclude 1.4 組件注冊名稱和組件聲明時name的區別1.4.1 組件聲明時…

nodejs-原型污染鏈

還是老規矩,邊寫邊學,先分享兩篇文章 深入理解 JavaScript Prototype 污染攻擊 | 離別歌 《JavaScript百煉成仙》 全書知識點整理-CSDN博客 Ctfshow web入門 nodejs篇 web334-web344_web334 ctfshow-CSDN博客 334-js審計 var express require(expr…

Oracle 數據庫通過exp/imp工具遷移指定數據表

項目需求:從prod數據庫遷移和復制2個表(BANK_STATE,HBS)的數據到uat數據庫環境。 數據庫版本:Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 遷移工具:客戶端exp/imp工具 -- 執行命令 從Prod數據庫導出數據exp us…

企業級基于SpringBoot的MQTT的構建和使用

基于SpringBoot的MQTT配置及使用 首先要使用EMQX搭建一個MQTT服務器&#xff0c;參考文檔&#xff1a;EMQX快速開始 本著開源分享的觀點&#xff0c;閑話不多說&#xff0c;直接上代碼 導入Maven <dependency><groupId>org.springframework.integration</gro…

26考研——圖_圖的代碼實操(6)

408答疑 文章目錄 五、圖的代碼實操圖的存儲鄰接矩陣結構定義初始化插入頂點獲取頂點位置在頂點 v1 和 v2 之間插入邊獲取第一個鄰接頂點獲取下一個鄰接頂點顯示圖 鄰接表結構定義初始化圖插入頂點獲取頂點位置在頂點 v1 和 v2 之間插入邊獲取第一個鄰接頂點獲取下一個鄰接頂點…

開源webmail郵箱客戶端rainloop的分支版本SnappyMail 設置發件人允許多重身份

RainLoop已多年未更新&#xff0c;SnappyMail 是 RainLoop 的分支&#xff0c;由社區維護。SnappyMail 不僅修復了漏洞&#xff0c;還增加了更多功能和優化。對 IMAP 支持更好&#xff0c;移動端體驗也比 RainLoop 更細致。 安裝過程和設置跟RainLoop一樣&#xff1a; 以寶塔面…