系列文章目錄
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入門知識點請關注該系列教程后續的更新。