摘要:開發(fā)傳送門開發(fā)教程交互事件一頭顯與手柄開發(fā)教程交互事件二使用開發(fā)教程深度剖析關(guān)于的開發(fā)調(diào)試方案以及原理機(jī)制開發(fā)教程標(biāo)準(zhǔn)入門使用開發(fā)場(chǎng)景的入門教程
Cardboard可以說是手機(jī)VR頭顯的元老了,狹義上指的是Google推出的一個(gè)帶有雙凸透鏡的盒子,廣義上則表示智能手機(jī)+盒子的VR體驗(yàn)平臺(tái)。
Cardboard與gaze注視它的交互方式較為簡單,利用了手機(jī)的陀螺儀,采用gaze注視行為來觸發(fā)場(chǎng)景里的事件,比如用戶在虛擬商店中注視一款商品時(shí),彈出這個(gè)商品的價(jià)格信息。
注視事件是WebVR最基本的交互方式,用戶通過頭部運(yùn)動(dòng)改變視線朝向,當(dāng)用戶視線正對(duì)著物體時(shí),觸發(fā)物體綁定的事件,具體分為三個(gè)基本事件,分別是gazeEnter,gazeTrigger,gazeLeave。
我們可以設(shè)置一個(gè)位于相機(jī)中心的準(zhǔn)心來描述這三個(gè)基本事件(準(zhǔn)確的說,在VR模式下是兩個(gè),分別位于左右相機(jī)的中心)
gazeEnter:當(dāng)準(zhǔn)心進(jìn)入物體時(shí),即用戶注視了物體,觸發(fā)一次
gazeLeave:當(dāng)準(zhǔn)心離開物體時(shí),即用戶停止注視該物體時(shí),觸發(fā)一次
gazeTrigger:當(dāng)準(zhǔn)心處于物體時(shí)觸發(fā),不同于gazeEnter,gazeTrigger會(huì)在每一幀刷觸發(fā),直到準(zhǔn)心離開物體
注視事件原理注視事件觸發(fā)條件其實(shí)就是物體被用戶視線“擊中”。在每幀動(dòng)畫渲染中,從準(zhǔn)心處沿z軸負(fù)方向發(fā)出射線,如果射線與物體相交,即物體被射線擊中,說明前方的物體被用戶注視,這里使用Three提供的raycaster對(duì)象,對(duì)場(chǎng)景里的3d物體進(jìn)行射線拾取。
下面是使用THREE.Raycaster拾取物體的簡單例子:
// 創(chuàng)建射線發(fā)射器實(shí)例raycaster const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(origin,camera); // 設(shè)置射線源點(diǎn) raycaster.intersectObjects(targetList); // 檢測(cè)targetList的object物體是否與射線相交 if (intersects.length > 0) { // 獲取從源點(diǎn)觸發(fā),與射線相交的首個(gè)物體 const target = intersects[0].object; // TODO }
主要分為三步:
new THREE.Raycaster()創(chuàng)建一個(gè)射線發(fā)射器;
調(diào)用.setFromCamera(origin,camera)設(shè)置射線發(fā)射源位置,第一個(gè)參數(shù)origin傳入NDC標(biāo)準(zhǔn)化設(shè)備坐標(biāo),即歸一化的屏幕坐標(biāo),第二個(gè)參數(shù)傳入相機(jī),此時(shí)射線將在屏幕的origin處,沿垂直于相機(jī)的近切面的方向進(jìn)行投射;
調(diào)用.intersectObjects(targetList)檢測(cè)targetList的物體是否相交
Raycaster借鑒了光線投射法進(jìn)行物體拾取,更多用法可參考three.js官方文檔
根據(jù)上文對(duì)gaze基本事件的描述,現(xiàn)在開始創(chuàng)建注視監(jiān)聽器Gazer類,提供事件綁定on、解綁off、更新update的公用方法,物體可注冊(cè)gazeEnter,gazeLeave,gazeTrigger事件回調(diào),以下是完整代碼。
// 注視事件監(jiān)聽器 class Gazer { constructor() { // 初始化射線發(fā)射源 this.raycaster = new THREE.Raycaster(); this._center = new THREE.Vector2(); this.rayList = {},this.targetList = []; this._lastTarget = null; } /** 物體綁定gaze事件的公用方法 * @param {THREE.Object3D} target 監(jiān)聽的3d網(wǎng)格 * @param {String} eventType 事件類型 * @param {Function} callback 事件回調(diào) **/ on(target, eventType, callback) { const noop = () => {}; // target首次綁定事件,則創(chuàng)建監(jiān)聽對(duì)象,加入raylist監(jiān)聽列表,并將三個(gè)基本事件的回調(diào)初始為空方法 if (!this.rayList[target.id]) this.rayList[target.id] = { target, gazeEnter: noop, gazeTrigger: noop, gazeLeave: noop }; // 根據(jù)傳入的 eventType與callback更新事件回調(diào) this.rayList[target.id][eventType] = callback; this.targetList = Object.keys(this.rayList).map(key => this.rayList[key].target); } off(target) { delete this.rayList[target.id]; this.targetList = Object.keys(this.rayList).map(key => this.rayList[key].target); } update(camera) { if (this.targetList.length <= 0) return; //更新射線位置 this.raycaster.setFromCamera(this._center,camera); const intersects = this.raycaster.intersectObjects(this.targetList); if (intersects.length > 0) { // 當(dāng)前幀射線擊中物體 const currentTarget = intersects[0].object; if (this._lastTarget) { // 上一幀射線擊中物體 if (this._lastTarget.id !== currentTarget.id) { // 上一幀射線擊中物體與當(dāng)前幀不同 this.rayList[this._lastTarget.id].gazeLeave(); this.rayList[currentTarget.id].gazeEnter(); } } else { // 上一幀射線未擊中物體 this.rayList[currentTarget.id].gazeEnter(); // 觸發(fā)當(dāng)前幀物體的gazeEnter事件 } this.rayList[currentTarget.id].gazeTrigger(); // 當(dāng)前幀射線擊中物體,觸發(fā)物體的gazeTrigger事件 this._lastTarget = currentTarget; } else { // 當(dāng)前幀我擊中物體 if ( this._lastTarget ) this.rayList[this._lastTarget.id].gazeLeave(); // 觸發(fā)上一幀物體gazeLeave this._lastTarget = null; } } }
下面一起來看Gazer實(shí)現(xiàn)的三步曲,這里用“擊中”表示射線與物體相交。
初始化射線發(fā)射器raycaster實(shí)例;
創(chuàng)建rayList以記錄注冊(cè)gaze事件的物體對(duì)象;
創(chuàng)建lastTarget記錄前一幀被射線擊中的物體,初始為null。
通過調(diào)用gazer.on(target,eventType,callback)方式,傳入綁定事件的Obect3D對(duì)象target,綁定事件類型eventType以及事件回調(diào)callback三個(gè)參數(shù)。
判斷這個(gè)target是否存在,不存在,則創(chuàng)建一個(gè)監(jiān)聽對(duì)象,存在則更新對(duì)象里的事件函數(shù)。這個(gè)對(duì)象包括傳入的target本身,以及三個(gè)基本事件的回調(diào)函數(shù)(初始值為空方法):
this.rayList[target.id] = { target, gazeEnter, gazeTrigger, gazeLeave }
將這個(gè)對(duì)象以鍵值對(duì)形式賦值給raylist[target.id]監(jiān)聽序列對(duì)象;
將raylist對(duì)象處理成[ target1, ..., targetN ]的形式賦值給this.targetList,作為raycaster.intersectObjects的入?yún)ⅰ?/p>
調(diào)用raycaster.setFromCamera更新射線起點(diǎn)與方向;
調(diào)用raycaster.intersectObjects檢測(cè)監(jiān)聽序列this.targetList是否有物體與射線相交;
根據(jù)gazeEnter和gazeLeave和gazeTrigger實(shí)現(xiàn)的情況,總結(jié)了以下這三個(gè)事件觸發(fā)的邏輯圖。
邏輯圖里的三個(gè)條件用代碼表示如下:
當(dāng)前幀射線是否擊中物體:if (intersects.length > 0)
上一幀射線是否擊中物體:if (this._lastTarget)
當(dāng)前幀射線擊中物體是否與上一幀不同:if (this._lastTarget.id !== currentTarget.id)
if (intersects.length > 0) { // 當(dāng)前幀射線擊中物體 const currentTarget = intersects[0].object; if (this._lastTarget) { // 上一幀射線擊中物體 if (this._lastTarget.id !== currentTarget.id) { // 上一幀射線擊中物體與當(dāng)前幀不同,觸發(fā)上一幀物體的gazeLeave事件,觸發(fā)當(dāng)前幀物體的gazeEnter事件 this.rayList[this._lastTarget.id].gazeLeave(); this.rayList[currentTarget.id].gazeEnter(); } } else { // 上一幀射線未擊中物體 this.rayList[currentTarget.id].gazeEnter(); // 上一幀射線沒有擊中物體,觸發(fā)當(dāng)前幀物體的gazeEnter事件 } this.rayList[currentTarget.id].gazeTrigger(); // 當(dāng)前幀射線擊中物體,觸發(fā)物體的gazeTrigger事件 this._lastTarget = currentTarget; } else { // 當(dāng)前幀我擊中物體 if ( this._lastTarget ) this.rayList[this._lastTarget.id].gazeLeave(); // 上一幀射線擊中物體,觸發(fā)上一幀物體gazeLeave this._lastTarget = null; }
最后,我們需要更新this._lastTarget值,供下一幀進(jìn)行邏輯判斷,如果當(dāng)前幀有物體擊中,則this._lastTarget = currentTarget,否則執(zhí)行this._lastTarget = null。
事件綁定示例接下來,我們調(diào)用前面定義的Gazer類開發(fā)gaze交互,實(shí)現(xiàn)一個(gè)簡單例子:隨機(jī)創(chuàng)建100個(gè)cube立方體,當(dāng)用戶注視立方體時(shí),立方體半透明。
首先創(chuàng)建準(zhǔn)心,設(shè)置為一個(gè)圓點(diǎn)作為展現(xiàn)給用戶的光標(biāo),當(dāng)然你可以創(chuàng)建其它準(zhǔn)心形狀,比如十字形或環(huán)形等。
// 創(chuàng)建準(zhǔn)心 createCrosshair () { const geometry = new THREE.CircleGeometry( 0.002, 16 ); const material = new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true }); const crosshair = new THREE.Mesh(geometry,material); crosshair.position.z = -0.5; return crosshair; }
接下來,在start()方法創(chuàng)建物體并綁定事件,在update監(jiān)聽事件。
// 場(chǎng)景物體初始化 start() { const { scene, camera } = this; ... 創(chuàng)建燈光、地板等 // 添加準(zhǔn)心到相機(jī) camera.add(this.createCrosshair()); this.gazer = new Gazer(); // 創(chuàng)建立方體 for (let i = 0; i < 100; i++) { const cube = this.createCube(2,2,2 ); cube.position.set( 100*Math.random() - 50, 50*Math.random() -10, 100*Math.random() - 50 ); scene.add(cube); // 綁定注視事件 this.gazer.on(cube,"gazeEnter",() => { cube.material.opacity = 0.5; }); this.gazer.on(cube,"gazeLeave",() => { cube.material.opacity = 1; }); } } // 動(dòng)畫更新 update() { const { scene, camera, renderer, gazer } = this; gazer.update(camera); renderer.render(scene, camera); }
在示例中,我們遵循上一期WebVRApp的代碼結(jié)構(gòu),在start方法里增加了一個(gè)準(zhǔn)心,為100個(gè)cube立方體綁定gazeEnter事件和gazeLeave事件,觸發(fā)gazeEnter時(shí),立方體半透明,觸發(fā)gazeLeave時(shí),立方體恢復(fù)不透明。
演示地址:yonechen.github.io/WebVR-helloworld/cardboard.html
源碼地址:github.com/YoneChen/WebVR-helloworld/blob/master/cardboard.html
注視事件除了以上三種基本事件外,還衍生了像注視延遲事件和注視點(diǎn)擊事件,這些gaze事件都可以在gazeTrigger里進(jìn)行拓展。
cardboard二代在盒子上提供了一個(gè)按鈕,當(dāng)用戶通過注視物體并點(diǎn)擊按鈕,由按鈕點(diǎn)擊屏幕觸發(fā)。
實(shí)現(xiàn)思路:在window綁定click事件,觸發(fā)click時(shí)改變標(biāo)志位,在gazeTrigger方法內(nèi)根據(jù)標(biāo)志位來判斷是否執(zhí)行回調(diào),關(guān)鍵代碼如下:
//按鈕事件監(jiān)聽 window.addEventListener("click", e => this.state._clicked = true); this.gazer.on(cube,"gazeTrigger",() => { // 當(dāng)用戶點(diǎn)擊時(shí)觸發(fā) if (this.state._clicked) { this.state._clicked = false; // 重置點(diǎn)擊標(biāo)志位 cube.scale.set(1.5,1.5,1.5); // TODO } });
當(dāng)準(zhǔn)心在物體上超過一定時(shí)間時(shí)觸發(fā),一般會(huì)在準(zhǔn)心處設(shè)置一個(gè)進(jìn)度條動(dòng)畫。
實(shí)現(xiàn)思路:在gazeEnter時(shí)記錄開始時(shí)間點(diǎn),在gazeTrigger計(jì)算出時(shí)間差是否超過預(yù)設(shè)延遲時(shí)間,如果是則執(zhí)行回調(diào),關(guān)鍵代碼如下:
//準(zhǔn)心進(jìn)入物體,開啟事件觸發(fā)計(jì)時(shí) this.gazer.on(cube,"gazeEnter",() => { this.state._wait = true; // 計(jì)時(shí)已開始 this.animate.loader.start(); // 開啟準(zhǔn)心進(jìn)度條動(dòng)畫 this.state.gazeEnterTime = Date.now(); // 記錄計(jì)時(shí)開始時(shí)間點(diǎn) }); this.gazer.on(cube,"gazeTrigger",() => { // 當(dāng)計(jì)時(shí)已開始,且延遲時(shí)長超過1.5秒觸發(fā) if (this.state._wait && Date.now() - this.state.gazeEnterTime > 1500) { this.animate.loader.stop(); // 停止準(zhǔn)心進(jìn)度條動(dòng)畫 this.state._wait = false; // 計(jì)時(shí)結(jié)束 cube.material.opacity = 0.5; // TODO } }); this.gazer.on(cube,"gazeLeave",() => { this.animate.loader.stop(); // 停止準(zhǔn)心進(jìn)度條動(dòng)畫 this.state._wait = false; // 計(jì)時(shí)結(jié)束 ... });
這里準(zhǔn)心計(jì)時(shí)進(jìn)度條loader動(dòng)畫使用了Tween.js,這里就不展開了,更多可在源碼地址查看。
小結(jié)演示地址:yonechen.github.io/WebVR-helloworld/cardboard2.html
源碼地址:github.com/YoneChen/WebVR-helloworld/blob/master/cardboard2.html
以上介紹了Cardboard的gaze事件概念與原理,以及三個(gè)基本事件的開發(fā)過程,通過例子展示gaze交互實(shí)現(xiàn)方法,最后文末補(bǔ)充了gaze事件的擴(kuò)展。
上文提及的注視點(diǎn)擊也是Gear VR最常用的交互方式,不過Gear VR提供了更為豐富的touchpad而不是按鈕,下一期將詳細(xì)介紹Gear VR與touchpad的事件開發(fā),敬請(qǐng)期待。
WebVR開發(fā)教程——交互事件(一)頭顯與手柄
WebVR開發(fā)教程——交互事件(二)使用Gamepad
WebVR開發(fā)教程——深度剖析 關(guān)于WebVR的開發(fā)調(diào)試方案以及原理機(jī)制
WebVR開發(fā)教程——標(biāo)準(zhǔn)入門 使用Three.js開發(fā)WebVR場(chǎng)景的入門教程
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/89436.html
摘要:返回的位置矩陣返回的方向矩陣返回軸每秒的角加速度返回軸每秒的角速度返回軸每秒的線性加速度返回軸的線性速度與只有的如和的只包含方向矩陣,因此為而為而的如和由于和兼具,因此和都為。 showImg(https://segmentfault.com/img/remote/1460000011814572?w=680&h=383);上期 WebVR開發(fā)教程——交互事件(一)頭顯與手柄 從頭顯和...
摘要:交互事件交互根據(jù)自由度可分為和,顯然,所有的頭顯都應(yīng)支持方向的追蹤。交互事件除了,現(xiàn)在大部分還搭配,用戶通過手持手柄可以與虛擬場(chǎng)景進(jìn)行交互。 showImg(https://segmentfault.com/img/remote/1460000011813767?w=880&h=471); 前兩期主要介紹了開發(fā)WebVR應(yīng)用的基本套路,不過開發(fā)出來的場(chǎng)景還只是可遠(yuǎn)觀而不可褻玩,缺乏交互...
摘要:在文末,我會(huì)附上一個(gè)可加載的模型方便學(xué)習(xí)中文藝術(shù)字渲染用原生可以很容易地繪制文字,但是原生提供的文字效果美化功能十分有限。 showImg(https://segmentfault.com/img/bVWYnb?w=900&h=385); WebGL 可以說是 HTML5 技術(shù)生態(tài)鏈中最為令人振奮的標(biāo)準(zhǔn)之一,它把 Web 帶入了 3D 的時(shí)代。 初識(shí) WebGL 先通過幾個(gè)使用 Web...
摘要:片元著色器主要處理片元顏色,在這里只是將紋理坐標(biāo)和紋理對(duì)象傳給片元著色器。根據(jù)公式分別計(jì)算出左右視口的模型視圖投影矩陣,傳給頂點(diǎn)著色器程序,與頂點(diǎn)緩沖區(qū)的頂點(diǎn)坐標(biāo)相乘繪制出最終頂點(diǎn)。 最近WebVR API 1.1已經(jīng)發(fā)布,2.0草案也在擬定中,在我看來,WebVR走向大眾瀏覽器是早晚的事情了,今天本人將對(duì)WebVR開發(fā)環(huán)境和開發(fā)流程進(jìn)行深入介紹。 WebVR與WebVR API 首先...
閱讀 3527·2021-11-23 10:13
閱讀 895·2021-09-22 16:01
閱讀 935·2021-09-09 09:33
閱讀 670·2021-08-05 09:58
閱讀 1748·2019-08-30 11:14
閱讀 2025·2019-08-30 11:02
閱讀 3301·2019-08-29 16:28
閱讀 1515·2019-08-29 16:09