本文旨在介紹如何搭建WebVR工程以支持多場景開發。css
首先,做爲一個基本的前端工程來講,咱們須要讓代碼「工程化」,不只要提供編譯構建、壓縮打包功能,還要讓每一個頁面模塊化;
延伸到WebVR工程,咱們也須要考慮就必須考慮「多頁面」模塊化,即提供多個場景模塊化開發,由於一個完整的WebVR App不單單隻有一個場景。這裏能夠參考google的WebVR多場景示例:https://vr.chromeexperiments....html
多場景開發,最簡單的方式就是,一個場景對應一份html、css、js,多個頁面須要多個html,每次頁面跳轉須要從新進行VR渲染進行初始化。
實際上咱們在多場景中,場景初始化只須要執行一次(好比,建立一個場景->建立相機->建立渲染器),咱們只須要一個index.html做爲入口頁面,將VR場景初始化、建立、回收、切換封裝成公用組件。前端
在首次進入場景時進行初始化,在須要場景切換時進行場景回收和按需加載,這樣一來,用戶切換場景時,不用把時間浪費在等待html和初始化場景上。基於以上思路,本人總結的一套WebVR工程搭建方案,供各位參考。node
項目地址:https://github.com/YorkChan94...
Demo:https://yorkchan94.github.io/...
相關技術棧:three.js
、webpack2
、es6/7
想詳細瞭解WebVR開發步驟,也歡迎參考個人文章《VR大潮來襲——前端開發能作些什麼》webpack
VR多場景模塊化開發git
支持VR場景建立、回收、切換es6
項目自動化構建與壓縮打包github
支持es7/6web
three.jschrome
vrcontrols.js
vreffect.js
webvr-manager.js
webvr-polyfill.js
three-onevent.js
webpack |-- webpack.config.js # 公共配置 |-- webpack.dev.js # 開發配置 |-- webpack.prod.js # 生產配置 src # 項目源碼 |-- page # WebVR場景目錄 | |-- index.js # WebVR入口場景 | |-- page1.js | |-- page2.js |-- common # 公共目錄,包括webvr封裝類和polyfill | |-- VRCore.js | |-- VRPage.js | |-- vendor.js |-- lib # vr三方插件,包括相機控制器和分屏器 | |-- vrcontrol.js | |-- vreffect.js |-- assets # 素材目錄,包括3d模型、紋理、音頻等 | |-- audio | |-- model | |-- texture |-- index.html # WebVR公用頁面 package.json READNE.md
咱們先來看看index.html,其實整個body就只有一個dom,用來append咱們的canvas
,畢竟因此場景都在canvas
裏運行。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no"> <title>webVR-INDEX</title> </head> <body> <section class="webvr-container"> </section> </body> </html>
有了公用html,咱們但願這樣開發WebVR應用,即一個場景對應一個js腳本,形如:
// 繼承VRPage父類,開發每個場景 import VRPage from 'common/js/VRPage'; class Page1 extends VRPage { start() { // 啓動渲染以前,建立場景3d模型 let geometry = new THREE.CubeGeometry(5,5,5); let material = new THREE.MeshBasicMaterial( { color:0x00aadd} ); this.box = new THREE.Mesh(geometry,material); this.box.position.set(3,-2,-3); WebVR.Scene.add(this.box); } loaded() { // 場景資源加載完畢,可執行音頻播放等。 } update(delta) { // 開啓渲染以後,執行模型動畫 this.box.rotation.y += 0.05; } } export default (() => { return new Page1(); })();
這裏參照了相似Unity3d和React的開發模式,在start方法裏建立3d模型,在update方法裏處理3d動畫,這樣的好處在於:
每個場景均可以進行獨立開發而互不影響;
一旦VR環境初始化以後,不須要在每次場景跳轉切換時從新初始化一遍。
VRCore.js做爲公用模塊管理整個webvr應用的全部子場景,包括場景初始化、VR相機渲染、場景切換、場景回收等靜態函數。
VRPage.js做爲每一個場景的工廠類,支持不一樣3d頁面(場景)之間的代碼獨立。
每個VR頁面的生命週期都是:建立物體->加載模型->啓動渲染的過程,所以,須要建立一個基類,來實現每個VR場景實例的生命週期。
//common/VRPage.js import * as WebVR from 'VRCore.js' //管理全部場景的公用模塊 // VR場景工廠 export default class VRPage { constructor(options={}) { // 建立場景,若是場景已初始化 WebVR.createScene(options); this.start(); this.loadPage(); } loadPage() { THREE.DefaultLoadingManager.onLoad = () => { // 模型加載完畢,即開啓渲染 WebVR.renderStart(this.update); this.loaded(); } } start() { // 實例的start方法將在啓動渲染以前,場景相機初始化後執行。 } loaded() { // 實例的loaded方法將在場景資源加載後執行。 } update(delta) { // 實例的update方法將在渲染器每一次渲染時執行。 } }
這裏使用THREE.DefaultLoadingManager.onLoad
方法監聽場景是否加載完畢,一旦加載完畢,便啓動渲染。
主要包括四個步驟
新建場景
建立VR相機
加載場景腳本與資源
開啓動畫渲染
function createScene({domContainer=document.body,fov=70,far=4000}) { // 建立場景 Scene = new THREE.Scene(); // 建立相機 Camera = new THREE.PerspectiveCamera(fov,window.innerWidth/window.innerHeight,0.1,far); Camera.position.set( 0, 0, 0 ); Scene.add(Camera); // 建立渲染器 Renderer = new THREE.WebGLRenderer({ antialias: true } ); Renderer.setSize(window.innerWidth,window.innerHeight); Renderer.shadowMapEnabled = true; Renderer.setPixelRatio(window.devicePixelRatio); domContainer.appendChild(Renderer.domElement); initVR(); resize(); }
首先是three.js開發三部曲,建立場景、相機、渲染器,接着調用initVR
函數來完成VR場景分屏和陀螺儀控制,WebVR基本開發步驟能夠參考。
function initVR() { // 初始化VR分屏器和控制器 Effect = new THREE.VREffect(Renderer); Controls = new THREE.VRControls(Camera); // 初始化VR管理器 Manager = new WebVRManager(Renderer, Effect); window.addEventListener( 'resize', e => { // 調整渲染器和相機以適應窗口拉伸時寬高變更 Camera.aspect = window.innerWidth / window.innerHeight; Camera.updateProjectionMatrix(); Effect.setSize(window.innerWidth, window.innerHeight); }, false ); }
// VRCore.js function renderStart(callback) { // 設置loopID變量記錄每一幀ID loopID = 0; const loop = () => { if(loopID === -1) return; loopID = requestAnimationFrame(loop); callback(); Controls.update(); Manager.render(Scene, Camera); }; loop(); }
這裏傳入參數動畫渲染作了三件事,使用loopID做爲整個VR應用的全局變量,記錄每一幀動畫的更新;更新相機控制器和VR渲染器,
主要包括四個步驟
暫停渲染
清空當前場景物體
請求並加載目標場景腳本與資源
重啓渲染
function renderStop() { if (loopID !== -1) { window.cancelAnimationFrame(loopID); loopID = -1; } }
function clearScene() { for(let i = Scene.children.length - 1; i >= 0; i-- ) { Scene.remove(Scene.children[i]); } }
切換到下一場景,咱們須要請求對應的場景腳本,這裏使用webpack2的import函數進行代碼分離,固然你也可使用require.ensure(filename => {require(filename)})
方法。
import(`page/${fileName}.js`);
最終將清空當前場景與請求加載目標場景功能封裝爲forward
跳轉方法,就能夠在頁面裏直接調用了。
// common/VRCore.js function forward(fileName) { renderStop(); clearScene(); import(`page/${fileName}.js`); } // page/index.js ... class Index extends VRPage { start() { let geometry = new THREE.CubeGeometry(5,5,5); let material = new THREE.MeshBasicMaterial({ color: 0x00aadd }); this.box = new THREE.Mesh(geometry,material); this.box.position.set(3,-2,-3); // add gaze eventLisenter this.box.on('gaze',mesh => { // gazeIn trigger WebVR.forward('page2.js'); }); WebVR.Scene.add(box); } } ... // page2.js class page2 extends VRPage { start() { this.addPanorama(1000, ASSET_TEXTURE_SKYBOX); } addPanorama(radius,path) { // create panorama let geometry = new THREE.SphereGeometry(radius,50,50); let material = new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader().load(path),side:THREE.BackSide } ); let panorama = new THREE.Mesh(geometry,material); WebVR.Scene.add(panorama); return panorama; } } export default (() => { return new page2(); })();
咱們在場景裏建立一個立方體,當凝視到該物體時,執行forward
方法跳轉至page2
場景。
至此,咱們的WebVR工程已經完成了一半,接下來,咱們使用Webpack2來構建咱們的工程。
開發環境和生產環境下webpack配置略有不一樣,這裏主要給出webpack的基本配置,具體可參考項目地址。
const path = require('path'); const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ProvidePlugin = require('webpack/lib/ProvidePlugin'); module.exports = { entry: { 'vendor': './src/common/js/vendor.js', 'app': './src/page/index.js' }, output: { path: path.resolve(__dirname, '../dist/'), filename: '[name].js', sourceMapFilename: '[name].map', chunkFilename: '[id]-chunk.js', publicPath: '/' },
這裏咱們將webvr首個場景src/page/index.js
做爲項目打包入口,同時將page目錄下的文件也做爲單獨chunk,配合按需加載來支持場景切換。
module: { rules: [ { test: /\.js/, exclude: /node_modules/, use: [ { loader:'babel-loader',options: { presets: ["latest",["es2015", {"modules": false}]] } ] }, { test: /\.css/, use: ['style-loader','css-loader'] }, { test: /\.(jpg|png|mp4|wav|ogg|obj|mtl|dae)$/, loader: 'file-loader' } ] },
這裏引入file-loader,這樣就能在場景裏直接import
須要用到的素材,以下。
//page/page2.js import ASSET_TEXTURE_SKYBOX from 'assets/texture/360_page2.jpg';
webpack相關的plugin配置以下
plugins: [ new CommonsChunkPlugin({ name: ['app', 'vendor'], minChunks: Infinity }), new ProvidePlugin({ 'THREE': 'three', 'WebVR': path.resolve(__dirname,'../src/common/js/VRCore.js') }), new HtmlWebpackPlugin({ inject: true, template: path.resolve(__dirname, '../src/index.html'), favicon: path.resolve(__dirname, '../src/favicon.ico') }) ] };
使用ProvidePlugin
將three.js
做爲公用模塊輸出,以省去在每一個腳本import THREE from 'three'
的重複工做,同時將管理全部場景的核心模塊VRCore.js
做爲全局公用模塊輸出。
使用HtmlWebpackPlugin
將公用的html打包到dist
目錄下。
最後是polyfill配置,咱們須要引入webvr-polyfill和babel-polyfill來分別支持webvr API和ES6 API,並做爲一個頁面獨立腳本。
// common/vendor.js import 'babel-polyfill'; import 'webvr-polyfill';
以上WebVR工程已經基本搭建完畢,歡迎各位提出寶貴意見,後續咱們將探索daydream和Oculus在webvr上的開發模式,敬請期待。
最後,獻上前幾天在google開發者網站上看到的:預測將來,不如創造將來。