2019,不管是不是VR元年,VR行業確實在這一年勢頭凶猛,VR設備跨越式發展,然而VR內容確相對滯後。強調獨占性的各大VR內容平臺,更是將開發商分割成了不同的陣營。 ...
2019,不管是不是VR元年,VR行業確實在這一年勢頭凶猛,VR設備跨越式發展,然而VR內容確相對滯後。強調獨占性的各大VR內容平臺,更是將開發商分割成了不同的陣營。這項新的設備媒介同時驅動了瀏覽器開發商對Web進行虛擬現實的支持,WebVR的發展正集中於不可思議的視覺體驗以及創建線上虛擬現實環境的工具之上。
WebVR 1.0 API 提議簡介
Mozilla VR團隊正致力於在瀏覽器中支持線上創作及顯示VR內容,並已獲得重大突破。通過與Google Chrome團隊[Brandon Jones]的密切合作, Mozilla團隊發佈了WebVR API 1.0版本。
本文主要討論的是本版本API的基本使用方式,這需要讀者能夠理解一些複雜的概念,例如:
【數學中的矩陣】
( https://en.wikipedia.org/wiki/Matrix_%28mathematics%29 )
此外,您也可以通過閱讀 [A-Frame]( https://aframe.io/ )
或者
[WebVR boilerplate]( https://github.com/borismus/webvr-boilerplate/ )以快速入門WebVR。
如何開始實施WebVR
想要從現在開始嗎?目前,開發者可以通過使用Brandon Jone的
[實驗版Chromium]
( https://drive.google.com/a/mozilla.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list )來做一些驗證性的實驗。
[three.js] (http://threejs.org/ )
[WebVR Polyfill]( https://github.com/borismus/webvr-polyfill/ )
VR體驗的構成要素
讓我們瞭解一下VR體驗的關鍵構成要素:
1. 我們所渲染的虛擬現實顯示的內容。
2. 用戶姿態,頭盔在空間中的位置及方向。
3. 眼部參數定義的視距立體間隔或立體視覺(stereo separation)以及視野範圍。
現在讓我們看一下使內容呈現在VR頭盔中的執行順序:
1. 使用`navigator.getVRDisplays()` 來檢索VR設備。
2. 創建一個canvas元素來渲染顯示內容。
3. 在canvas元素中,使用`VRDisplay.requestPresent()`。
4. 創建一個VR設備特定的動畫迴圈,以執行內容渲染。
1)使用`VRDisplay.getPose()` 更新用戶動作。
2)進行計算和渲染。
3)使用 `VRDisplay.submitFrame()` 來告訴合成器,canvas元素準備好在VR設備顯示的時間。
下述部分描述了每一個行為的具體細節。
關於VR顯示設備
VR顯示設備對於顯示有著許多非常特殊的需求,比如:
[幀率(frame rate)]( https://en.wikipedia.org/wiki/Frame_rate ),
[視野範圍(field of view)]
( https://en.wikipedia.org/wiki/Field_of_view ),以及從標準桌面顯示中,拆分獨立處理的內容表現方式。
檢索VR顯示設備
為了使得使用瀏覽器進行VR設備檢索成為可能,可以使用`navigator.getVRDisplays()`方法,該方法返回一個由包含一系列`VRDisplay`對象的數組構成的約定(Promise):
…
navigator.getVRDisplays().then(function (displays) {
if (!displays.length) {
// 支持WebVR,沒有發現任何VR顯示設備。
return;
}
//處理 VRDisplay 對象 (作為全局變數)。
vrDisplay = displays.length[0];
}).catch(function (err) {
console.error('Could not get VRDisplays', err.stack);
});
…
註意:
* 您必須在對VR設備進行檢索之前,保證您的VR頭盔已經接入並開啟。
* 如果您沒有VR頭盔,你可以通過將`about:config`打開以及設置`dom.vr.cardboard.enabled`的值為`true`,來進行模擬模擬。
* [Firefox Nightly for Android]( https://nightly.mozilla.org/#Android )的用戶或者 [Firefox for iOS]( https://www.mozilla.org/en-US/firefox/ios/ )的用戶可以通過使用
[Google Cardboard]( https://www.google.com/get/cardboard/ )進行CardBorad設備的檢索。
創建渲染對象
為確定渲染對象的大小(比如,你的canvas大小),請創建一個能夠覆蓋雙眼視野範圍的渲染對象。為確定每隻眼睛的視野範圍(以像素為單位),參考以下代碼:
…
// 使用 'left'(左) 或 'right'(右).
var eyeParameter = vrDisplay.getEyeParameters('left');
var width = eyeParameter.renderWidth;
var height = eyeParameter.renderHeight;
…
將內容顯示在VR頭盔中
為了讓內容呈現在頭盔中,你需要使用`VRDisplay.requestPresent()`方法,該方法使用了WebGL元素作為參數,表示要顯示的可視面。
為了防止API被冗餘調用,瀏覽器需要一個用戶初始化事件,確保用戶是第一次進入VR模式。換句話說,用戶必須選擇開啟VR,所以我們將“進入VR”設定成一個按鈕作為`click`事件觸發。
…
// 選擇 WebGL canvas元素。
var webglCanvas = document.querySelector('#webglcanvas');
var enterVRBtn = document.querySelector('#entervr');
enterVRBtn.addEventListener('click', function () {
// 將當前的WebGL canvas設置為VR顯示。
vrDisplay.requestPresent({source: webglCanvas});
});
// 退出當前顯示。
vrDisplay.exitPresent();
…
設備特定的 `requestAnimationFrame`
既然我們建立了渲染對象,以及必要的渲染所需參數使得內容可以在頭盔中正確地顯示出來,我們現在可以創建一個場景的迴圈渲染。
我們希望通過使用回調方法`VRDisplay.requestAnimationFrame`以此來優化VR顯示設備的刷新率:
…
var id = vrDisplay.requestAnimationFrame(onAnimationFrame);
function onAnimationFrame () {
// 迴圈渲染。
id = vrDisplay.requestAnimationFrame(onAnimationFrame);
}
// 結束渲染迴圈。
vrDisplay.cancelRequestAnimationFrame(id);
…
這與你所熟悉的標準回調函數 `window.requestAnimationFrame()`的用法是一致的。我們使用這個回調函數以適應顯示內容中動作的位置和方向的不斷更新,並對VR顯示進行渲染。
從VR顯示中捕獲動作姿態信息
我們需要通過使用`VRDisplay.getPose()`方法來捕獲頭盔的位置和方向:
…
var pose = vrDisplay.getPose();
// 返回一個四元組。
var orientation = pose.orientation;
// 返回一個三維絕對位置向量。
var position = pose.position;
…
請註意:
* 如果無法確定位置及方向,這些信息將返回`null`。
更多詳情請參考 [VRStageCapabilities]
( https://mozvr.github.io/webvr-spec/#interface-vrdisplaycapabilities )以及 [VRPose]( https://mozvr.github.io/webvr-spec/#interface-vrpose )。
投射場景到VR顯示
為了使頭盔中的場景準確的進行立體渲染,我們需要提供一些眼部參數,例如眼部偏移量(基於 [瞳距(interpupillary distance or IPD)]
( https://en.wikipedia.org/wiki/Interpupillary_distance ),以及視野範圍(FOV)。
…
// 用左右眼參數之一作為參數傳入.
var eyeParameters = vrDisplay.getEyeParameters('left');
// 根據VRPose翻譯完世界坐標之後,繼續減去眼部偏移量進行變換
var eyeOffset = eyeParameters.offset;
// 使用映射矩陣進行映射。
var eyeMatrix = makeProjectionMatrix(vrDisplay, eyeParameters);
// 將eyeMatrix顯示到你的view中。
…
/**
* 生成映射矩陣
* @param {object} display - VRDisplay
* @param {number} eye - VREyeParameters
* @returns {Float32Array} 4×4 映射矩陣
*/
function makeProjectionMatrix (display, eye) {
var d2r = Math.PI / 180.0;
var upTan = Math.tan(eye.fieldOfView.upDegrees * d2r);
var downTan = Math.tan(eye.fieldOfView.leftDegrees * d2r);
var rightTan = Math.tan(eye.fieldOfView.rightDegrees * d2r);
var leftTan = Math.tan(eye.fieldOfView.leftDegrees * d2r);
var xScale = 2.0 / (leftTan + rightTan);
var yScale = 2.0 / (upTan + downTan);
var out = new Float32Array(16);
out[0] = xScale;
out[1] = 0.0;
out[2] = 0.0;
out[3] = 0.0;
out[4] = 0.0;
out[5] = yScale;
out[6] = 0.0;
out[7] = 0.0;
out[8] = -((leftTan - rightTan) * xScale * 0.5);
out[9] = (upTan - downTan) * yScale * 0.5;
out[10] = -(display.depthNear + display.depthFar) / (display.depthFar - display.depthNear);
out[12] = 0.0;
out[13] = 0.0;
out[14] = -(2.0 * display.depthFar * display.depthNear) / (display.depthFar - display.depthNear);
out[15] = 0.0;
return out;
}
…
向頭盔提交幀
VR對於降低用戶運動產生的不連續性做了一定的優化。這對舒服的體驗(不使人眩暈)至關重要。直接使用`VRDisplay.getPose()` 和 `VRDisplay.submitFrame()`方法來對這一現象進行控制:
…
// 渲染和計算不依賴於姿態。
// ...
var pose = vrDisplay.getPose();
// 在此將你生成的眼部矩陣適配到view中。
// 儘量在此處減少操作數量。
// ...
vrDisplay.submitFrame(pose);
//在提交之後對幀作出的任何操作不會給VR帶來延遲。此時可以渲染其他的view。
// ...
…
一般而言,正確的姿勢是,儘可能早的調用`VRDisplay.submitFrame()`方法,並儘可能晚地調用`VRDisplay.getPose()`方法。
### 示例Demo
https://toji.github.io/webvr-samples/