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