溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Webgl&Three.js中物體拾取的示例分析

發(fā)布時(shí)間:2021-09-22 10:48:27 來源:億速云 閱讀:151 作者:小新 欄目:web開發(fā)

小編給大家分享一下Webgl&Three.js中物體拾取的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

1、引子:

在傳統(tǒng)的web開發(fā)中,由于存在DOM樹以及事件捕獲冒泡等機(jī)制,我們可以很方便的在某個(gè)DOM節(jié)點(diǎn)上注冊事件,并且執(zhí)行父元素事件代理等一系列操作。但是在webgl的三維世界里,用戶使用鼠標(biāo)或者touch事件,事件接收方是canvas容器,如何將這種點(diǎn)擊行為映射到三維世界,就需要借助三維世界的能力了,并且建立從canvas平面容器到三維世界的橋梁,進(jìn)行所謂的物體拾取

2、基礎(chǔ)知識:

DOM與NDC坐標(biāo)轉(zhuǎn)換,相機(jī)射線,觀察者模式。熟悉這部分知識的同學(xué),可以直接跳過到下面的代碼實(shí)現(xiàn)。

  • a、DOM坐標(biāo)和NDC坐標(biāo)轉(zhuǎn)換:在這里,我們知道DOM坐標(biāo)的(0,0)點(diǎn)在容器左上角,NDC坐標(biāo)的(0,0)點(diǎn)在容器中心,需要進(jìn)行坐標(biāo)轉(zhuǎn)換。具體定義可以參考下面兩個(gè)鏈接

三維坐標(biāo)變換 屏幕坐標(biāo)定義

  • b、相機(jī)射線:  根據(jù)Three.js的官方定義,射線就是用來做物體拾取的機(jī)制。相比較于傳統(tǒng)的顏色拾取,射線拾取可以識別多個(gè)物體,得到先后順序,在使用上更加方便并且符合人的直覺。?  下面看一個(gè)官方的code example?

javascript

const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); function onMouseMove( event ) {     // 這里實(shí)現(xiàn)了DOM坐標(biāo)系到NDC坐標(biāo)系的轉(zhuǎn)換     mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;     mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; } function render() {     // 從相機(jī)位置,發(fā)出一條射線     raycaster.setFromCamera( mouse, camera );     // 檢測相機(jī)發(fā)出射線相交的obj列表     const intersects = raycaster.intersectObjects( scene.children );     for ( let i = 0; i < intersects.length; i ++ ) {         // 將相交物體的材質(zhì)顏色設(shè)置為紅色         intersects[ i ].object.material.color.set( 0xff0000 );     }     renderer.render( scene, camera ); } window.addEventListener( 'mousemove', onMouseMove, false ); window.requestAnimationFrame(render);

c、觀察者模式:與dom事件機(jī)制類似,觀察者模式非常是適合做這種注冊-觸發(fā)機(jī)制的。

3、具體實(shí)現(xiàn):

考慮到每次遍歷intersects數(shù)組非常的不方便,特別是當(dāng)場景中有實(shí)際上百個(gè)Object3D的時(shí)候。所以這里我們定義一個(gè)全局的對象存儲注冊事件,然后修改Object3D的原型鏈,增加on和on和on和off方法,來實(shí)現(xiàn)類似于DOM元素的事件注冊和銷毀。

const globalEvent = {click: {}} Object.assign(Object3D.prototype, {     $on(eventType, cb) {        if(globalEvent.hasOwnProperty(eventType)) {             globalEvent[eventType][this.id] = {                 object3d: this,                 callback: cb             };        } else { // error warn}     }     $off(eventType) {         if (!eventType) throw new Error('')         if(globalEvent.hasOwnProperty(eventType)) {             delete globalEvent[eventType][this.id]         } else {             throw new Error('')         }     } }) init(camera) function init(camera, container) {     let intersectPoint, obj, mouseX, mouseY, clicked;     const targetObj = globalEvent.click     const rayCaster = new Raycaster();     function down(e) {         obj = null;         e.preventDefault();         mouseX = event.clientX;         mouseY = event.clientY;         if (!globalEvent.click) return;         rayCaster.setFromCamera(             new Vector2(                 (mouseX / window.innerWidth) * 2 - 1,                 -(mouseY / window.innerHeight) * 2 + 1             ),             camera         );          let intersects = rayCaster.intersectsObjects(getVisibleList(targetObj));         if (intersects.length > 0) {             if (clicked) {                 obj = null;                 return;             }             clicked = true;             obj = intersects[0].object;             intersectPoint = intersects[0].point;                                 } else {             clicked = false;         }     }     function move(e) {         event.preventDefault();         // 這里針對移動端做一些優(yōu)化      }     function up(e) {         event.preventDefault();         if (clicked && !!obj && obj.callback) {             obj.callback(obj.object3d, intersectPoint);         }         clicked = false     }    const eventOption = {      passive: false    };    container.addEventListener('mousedown', down, {passive: false});    container.addEventListener('mousemove', move, {passive: false});    container.addEventListener('mouseup', up, {passive: false});    container.addEventListener('touchstart', down, {passive: false});    container.addEventListener('touchmove', move, {passive: false});    container.addEventListener('touchend', up, {passive: false});}  function getVisibleList(targetObj) {     const list = []     for (const key in targetObj) {         const target = targetObj[key].object3d;         if (target.visible) list.push(target);     }     return list }  /*     使用方式:直接在mesh上注冊事件     target: 命中物體     point: 命中點(diǎn)的三維坐標(biāo) */ mesh.$on('click', (target, point)) {  }

以上是“Webgl&Three.js中物體拾取的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI