您好,登錄后才能下訂單哦!
小編給大家分享一下Webgl&Three.js中物體拾取的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
在傳統(tǒng)的web開發(fā)中,由于存在DOM樹以及事件捕獲冒泡等機(jī)制,我們可以很方便的在某個(gè)DOM節(jié)點(diǎn)上注冊事件,并且執(zhí)行父元素事件代理等一系列操作。但是在webgl的三維世界里,用戶使用鼠標(biāo)或者touch事件,事件接收方是canvas容器,如何將這種點(diǎn)擊行為映射到三維世界,就需要借助三維世界的能力了,并且建立從canvas平面容器到三維世界的橋梁,進(jìn)行所謂的物體拾取
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ī)制的。
考慮到每次遍歷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è)資訊頻道!
免責(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)容。