WebVR開發(fā)教程——深度剖析

最近WebVR API 1.1已經(jīng)發(fā)布,2.0草案也在擬定中,在我看來,WebVR走向大眾瀏覽器是早晚的事情了,今天本人將對(duì)WebVR開發(fā)環(huán)境和開發(fā)流程進(jìn)行深入介紹。

WebVR與WebVR API

首先,WebVR指的是使用瀏覽器體驗(yàn)VR的方式,如今已經(jīng)成為了一種開放標(biāo)準(zhǔn)。
它提供了JavaScript API,使開發(fā)者可以獲取vr設(shè)備的輸入信息,來改變用戶在虛擬空間里的位置、視覺、行為等。
以下是目前主流VR及瀏覽器對(duì)WebVR的支持情況。

VR平臺(tái) 瀏覽器支持
Cardboard Chrome、百度VR瀏覽器
Daydream Chrome
Gear VR Oculus Browser或 Samsung Internet
Oculus Rift Firefox 或 Chrome
HTC Vive Firefox 或 Chrome 或 Servo
Vive Focus Chrome 或 Vive Browser
daydream view

遺憾的是,WebVR的體驗(yàn)方式目前只能運(yùn)行在Android和Windows系統(tǒng)上。不過這并不影響我們?cè)趍ac和linux上開發(fā)與調(diào)試。

Gear VR 5

WebVR的開發(fā)環(huán)境配置

由于WebVR App需要運(yùn)行VR設(shè)備上,而目前購買一臺(tái)VR設(shè)備的成本不低,所以這里我總結(jié)了一套開發(fā)環(huán)境下WebVR調(diào)試方案。
首先我們需要給WebVR靜態(tài)頁面起一個(gè)web server,這里我安裝 Web Server for Chrome,你也可以使用node或者上傳至github托管。

PC端調(diào)試

1. 安裝chrome擴(kuò)展程序 WebVR API Emulation

使用WebVR API Emulation擴(kuò)展程序可以模擬VR設(shè)備用戶的視角、位置等。

WebVR API Emulation模擬VR體驗(yàn)

移動(dòng)端調(diào)試

適用于cardboard級(jí)別的WebVR App調(diào)試。

1. 安裝chrome beta

目前需要webvr還屬于早期實(shí)驗(yàn)階段,需要下載chrome beta最新版,安裝完需要手動(dòng)開啟webvr支持,在瀏覽器地址欄輸入chrome://flags#enable-webvr,點(diǎn)擊啟用并重新啟動(dòng)chrome。

2. 安裝Google VR 服務(wù)

這是google給cardboard、daydream用戶提供VR服務(wù)配置,可以提供VR模式窗口,如下圖。
最后你可以在chrome上打開WebVR示例頁面驗(yàn)證是否配置成功

WebVR示例應(yīng)用
3. chrome inspector調(diào)試

通過手機(jī)chrome訪問我們開發(fā)的WebVR頁面,在PC端chrome輸入chrome://inspector進(jìn)行調(diào)試,具體可以參考 遠(yuǎn)程調(diào)試 Android 設(shè)備使用入門

完成WebVR開發(fā)環(huán)境配置之后,我們將正式進(jìn)入WebVR開發(fā)之旅。


使用WebGL開發(fā)WebVR

WebVR App實(shí)現(xiàn)依賴于WebGL技術(shù)開發(fā),WebGL是在瀏覽器上創(chuàng)建和運(yùn)行3D圖像,它遵循OpenGL ES的規(guī)范,通過GLSL語言操作GPU進(jìn)行頂點(diǎn)片元渲染。

3維模型矩陣變換

在WebGL場(chǎng)景中,3d物體都是通過矩陣變換最終形成屏幕上的2d圖像,[投影矩陣ProjectionMatrix] × [視圖矩陣ViewMatrix] × [模型矩陣ModelMatrix] × 頂點(diǎn)坐標(biāo),其中投影矩陣和視圖矩陣可以抽象為3d場(chǎng)景中的相機(jī)屬性。

模型矩陣 × 頂點(diǎn)坐標(biāo)(相對(duì)模型) = 頂點(diǎn)世界坐標(biāo)(絕對(duì)坐標(biāo))
視圖矩陣 × 世界坐標(biāo) = 頂點(diǎn)相機(jī)坐標(biāo)(以相機(jī)為坐標(biāo)原點(diǎn))
投影矩陣 × 頂點(diǎn)相機(jī)坐標(biāo) = 2d屏幕坐標(biāo)

相比一般WebGL場(chǎng)景,WebVR App不同之處在于:

  1. WebVR需要進(jìn)行雙屏渲染,通過分屏模擬人左右眼視野,因此在每一幀動(dòng)畫渲染中,WebVR應(yīng)用都要比普通WebGL應(yīng)用多繪制一次;
左側(cè)視圖和右側(cè)視圖的相對(duì)變形,模擬左右眼的視覺差異,來產(chǎn)生3d效果
  1. WebVR場(chǎng)景相機(jī)的方向、視野、位置(DOF)與用戶頭顯(HMD)緊密關(guān)聯(lián)。
    換句話說,當(dāng)用戶的現(xiàn)實(shí)視角發(fā)生變化時(shí),WebVR場(chǎng)景的相機(jī)也需要?jiǎng)討B(tài)變化。

根據(jù)以上不同之處,我梳理了一個(gè)WebVR App的簡單開發(fā)流程,如下圖。

WebVR開發(fā)流程

開發(fā)流程總結(jié)為: VR數(shù)據(jù)初始化 → WebGL初始化 → 動(dòng)畫渲染。

一、VR數(shù)據(jù)初始化

使用navigator.getVRDisplay()方法獲取VR實(shí)例,該方法返回值是一個(gè)promise實(shí)例,通過.then(function(displays){})取得當(dāng)前使用的VR實(shí)例列表。

let vrDisplay;
navigator.getVRDisplays().then(displays => {
  if (displays.length > 0) {
    vrDisplay = displays[0];
    console.log('Display found',vrDisplay);
    drawVRScene();
  } else {
    console.log('Display not found');
    // 非VR模式下渲染
    // drawScene();
  }
});

二、WebGL初始化

WebGL程序初始化一般分為這幾個(gè)步驟:編譯頂點(diǎn)、片元著色器程序→創(chuàng)建頂點(diǎn)、紋理緩沖區(qū)→ 設(shè)置畫布被清空時(shí)顏色→啟動(dòng)深度測(cè)試

function drawVRScene() {
  const canvas = document.getElementById('glcanvas');
  // 獲取WebGL上下文
  const gl = canvas.getContext('webgl');
  // WebGL初始化
  init(gl); 
  // WebGL渲染
  render(gl);
}
function init(gl) {
  // 預(yù)編譯著色器程序
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }
  // 創(chuàng)建頂點(diǎn)緩沖
  initVertexBuffers(gl);
  // 創(chuàng)建紋理緩沖
  initTextures(gl,'../assets/texture.jpg');
  gl.clearColor(0.4, 0.4, 0.4, 1.0);
  // 啟動(dòng)深度測(cè)試
  gl.enable(gl.DEPTH_TEST);
  gl.depthFunc(gl.LEQUAL);
}    

GLSL著色器程序

頂點(diǎn)著色器要做的工作是將Js輸入的頂點(diǎn)坐標(biāo)、模型-視圖-投影矩陣進(jìn)行逐頂點(diǎn)運(yùn)算。

const VSHADER_SOURCE = `
attribute vec4 a_Position;
uniform mat4 u_MvpMatrix;
attribute vec2 a_TexCoord;
varying highp vec2 v_TexCoord;
void main() {
    gl_Position = u_MvpMatrix * a_Position;
    v_TexCoord = a_TexCoord;
}
`;

片元著色器主要處理片元顏色,在這里只是將紋理坐標(biāo)和紋理對(duì)象傳給片元著色器。

const FSHADER_SOURCE = `
uniform sampler2D u_Sampler;
varying highp vec2 v_TexCoord;
void main() {
    gl_FragColor = texture2D(u_Sampler,v_TexCoord);
}
`;

WebVR前期初始化之后,我們需要?jiǎng)?chuàng)建動(dòng)畫來渲染VR場(chǎng)景。

三、動(dòng)畫渲染

1. requestAnimationFrame創(chuàng)建動(dòng)畫

通過使用vrDisplay實(shí)例的requestAnimationFrame(callback),遞歸執(zhí)行callback函數(shù)。
該方法是window.requestAnimationFrame的一個(gè)特殊實(shí)現(xiàn),它會(huì)優(yōu)先使用VR設(shè)備原生的刷新率而不是瀏覽器的刷新率,以達(dá)到合適的渲染幀頻。


function render(gl,vrDisplay) {
    // 創(chuàng)建VR幀數(shù)據(jù)對(duì)象
    const frameData = new VRFrameData();
    const u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    function animate() {
      // TODO
      draw(frameData,u_MvpMatrix);
      // 通過遞歸的方式,執(zhí)行繪圖函數(shù),產(chǎn)生動(dòng)畫
      vrDisplay.requestAnimationFrame(animate);
    }
    animate();
}

我們?cè)趩?dòng)動(dòng)畫遞歸之前使用new VRFrameData()方法,VRFrameData是WebVR提供的幀數(shù)據(jù)封裝對(duì)象,是WebVR渲染的關(guān)鍵數(shù)據(jù),下文將會(huì)介紹如何使用它。

2. VR渲染

2.1 使用viewport設(shè)置雙視口

WebGL上下文提供了viewport函數(shù),用來指定3d場(chǎng)景在canvas的繪制位置和尺寸。
默認(rèn)的情況下,WebGL渲染視口為gl.viewport(0, 0, canvas.width, canvas.height)
其中前兩個(gè)參數(shù)代表渲染的起點(diǎn)坐標(biāo),后兩個(gè)參數(shù)代表渲染的尺寸,這里通過依次設(shè)置左右眼渲染視口,來達(dá)到分屏效果。

function draw(frameData,u_MvpMatrix) {
  gl.viewport(0, 0, canvas.width * 0.5, canvas.height); // 設(shè)置左側(cè)視口
  // TODO
  gl.viewport(canvas.width * 0.5, 0, canvas.width * 0.5, canvas.height); // 設(shè)置右側(cè)視口
  // TODO
}

左、右側(cè)視口的渲染寬度為canvas寬度的1/2,左視口起始點(diǎn)為(0,0),右視口的起始點(diǎn)坐標(biāo)為(canvas.width * 0.5, 0)

2.2 使用VRFrameData動(dòng)態(tài)渲染

前面介紹了WebVR渲染需要根據(jù)用戶行為動(dòng)態(tài)繪制每一幀場(chǎng)景,具體做法是:
1)通過WebVR API提供的VRFrameData實(shí)例獲取當(dāng)前幀的視圖矩陣和投影矩陣;
2)將視圖-投影矩陣傳入著色器進(jìn)行繪制;
3)生成下一幀數(shù)據(jù)并提交給當(dāng)前canvas;
4)進(jìn)入下一幀回調(diào)。
具體代碼如下

function draw(gl,frameData,u_MvpMatrix) {       
  const {
    leftProjectionMatrix,
    leftViewMatrix,
    rightProjectionMatrix,
    rightViewMatrix
  } = frameData; 
  // 初始化模型矩陣,模型-視圖-投影矩陣
  let modelMatrix = mat4.create(),
      vpMatrix = mat4.create(),
      mvpMatrix = mat4.create();

  // 將左眼視圖渲染到畫布的左側(cè)
  gl.viewport(0, 0, canvas.width * 0.5, canvas.height);
  // mvpMatrix = ProjectionMatrix × ViewMatrix × modelMatrix
  // 這里使用gl-matrix.js的mat4對(duì)象對(duì)float32Array進(jìn)行矩陣操作
  mat4.multiply(vpMatrix,leftProjectionMatrix,leftViewMatrix);
  mat4.multiply(mvpMatrix,vpMatrix,modelMatrix);
  // 將模型-視圖-投影矩陣mvpMatrix傳入著色器
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix);
  // 左側(cè)繪圖
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);

  // 將右眼視圖渲染到畫布的右側(cè)
  gl.viewport(canvas.width * 0.5, 0, canvas.width * 0.5, canvas.height);
  mat4.multiply(vpMatrix,rightProjectionMatrix,rightViewMatrix);
  mat4.multiply(mvpMatrix,vpMatrix,modelMatrix);
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix);
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);

  // 生成下一幀數(shù)據(jù)并覆蓋原來的frameData
  vrDisplay.getFrameData(frameData);
  vrDisplay.submitFrame();
}

首先,在動(dòng)畫渲染前通過new VRFrameData()獲得實(shí)例frameData,并傳入動(dòng)畫渲染函數(shù);
接著,在動(dòng)畫函數(shù)里獲取frameData的屬性:

VRFrameData屬性 類型 說明
leftProjectionMatrix Float32Array 左視口投影矩陣
leftViewMatrix Float32Array 左視口視圖矩陣
rightProjectionMatrix Float32Array 右視口投影矩陣
rightViewMatrix Float32Array 右視口視圖矩陣

當(dāng)然VRFrameData還包括VRPose屬性這里就不一一列舉了。
根據(jù)公式分別計(jì)算出左右視口的模型-視圖-投影矩陣,傳給頂點(diǎn)著色器程序,與頂點(diǎn)緩沖區(qū)的頂點(diǎn)坐標(biāo)相乘繪制出最終頂點(diǎn)。

MvpMatrix = ProjectionMatrix × ViewMatrix × modelMatrix

最后,在每一幀動(dòng)畫回調(diào)結(jié)束前,我們調(diào)用vrDisplay.getFrameData(frameData)來生成下一幀數(shù)據(jù)并覆蓋frameData,并使用vrDisplay.submitFrame()將當(dāng)前幀提交給當(dāng)前畫布渲染。

至此,WebVR的開發(fā)流程已基本走完,具體代碼可以參考下方demo


項(xiàng)目代碼:https://github.com/YoneChen/webvr-demo

結(jié)語:使用原生WebGL開發(fā)WebVR應(yīng)用相比three.js或者aframe代碼要復(fù)雜很多,不過通過這種方式卻能更深入的了解WebVR的工作原理。

想了解three.js開發(fā)webvr,可參考《VR大潮來襲---前端開發(fā)能做些什么》
也歡迎各位關(guān)注我的專欄 WebVR技術(shù)莊園,不定期更新~

相關(guān)資料:
計(jì)算機(jī)圖形知識(shí):矩陣變換
WebGL快速入門:WebGL 技術(shù)儲(chǔ)備指南
谷歌開發(fā)者 | WebVR:WebVR基本原理
MDN | WebVR API:使用WebVR API

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,076評(píng)論 25 708
  • 作者著眼于WebVR的發(fā)展,通過介紹當(dāng)前WebVR的現(xiàn)狀,來幫助我們認(rèn)識(shí)當(dāng)下的技術(shù)環(huán)境;介紹了伴隨發(fā)展的硬件,也一...
    ShusQ閱讀 1,308評(píng)論 0 3
  • WebGL從2012年開始接觸,后面因?yàn)殚_始專注前端其他方面的事情,慢慢地就把它給遺忘。最近前端開始又流行起繪畫制...
    我不是傳哥閱讀 4,133評(píng)論 1 22
  • 第二章 我們的類型選擇 在大多數(shù)面向?qū)ο蟮木幊陶Z言中我們創(chuàng)建的類是引用類型,它作為我們對(duì)象的藍(lán)圖。不像其它面向?qū)ο?..
    焉知非魚閱讀 535評(píng)論 7 0
  • 最近看《華爾街日?qǐng)?bào)是如何講故事的》一書。書中作者的很多觀點(diǎn)對(duì)于剛開始寫文章的朋友有很大的啟發(fā)和幫助。現(xiàn)把其中的要點(diǎn)...
    琬喬閱讀 1,136評(píng)論 2 15