本文旨在介绍如何搭建WebVR工程以支持多场景开发。
首先,作为一个基本的前端工程来说,我们需要让代码“工程化”,不仅要提供编译构建、压缩打包功能,还要让每个页面模块化;
延伸到WebVR工程,我们也需要考虑就必须考虑“多页面”模块化,即提供多个场景模块化开发,因为一个完整的WebVR App不仅仅只有一个场景。这里可以参考google的WebVR多场景示例:https://vr.chromeexperiments.com/
多场景开发,最简单的方式就是,一个场景对应一份html、css、js,多个页面需要多个html,每次页面跳转需要重新进行VR渲染进行初始化。
实际上我们在多场景中,场景初始化只需要执行一次(比如,创建一个场景->创建相机->创建渲染器),我们只需要一个index.html作为入口页面,将VR场景初始化、创建、回收、切换封装成公用组件。
在首次进入场景时进行初始化,在需要场景切换时进行场景回收和按需加载,这样一来,用户切换场景时,不用把时间浪费在等待html和初始化场景上。基于以上思路,本人总结的一套WebVR工程搭建方案,供各位参考。
项目地址:https://github.com/YoneChen/webvr-webpack2-boilerplate
Demo:https://YoneChen.github.io/webvr-webpack2-boilerplate/dist/
相关技术栈:three.js
、webpack2
、es6/7
想详细了解WebVR开发步骤,也欢迎参考我的文章《VR大潮来袭——前端开发能做些什么》
实现功能
- VR多场景模块化开发
- 支持VR场景创建、回收、切换
- 项目自动化构建与压缩打包
- 支持es7/6
WebVR相关库
- three.js
- vrcontrols.js
- vreffect.js
- webvr-manager.js
- webvr-polyfill.js
- three-onevent.js
主要目录结构
1 | webpack |
我们先来看看index.html,其实整个body就只有一个dom,用来append我们的canvas
,毕竟所以场景都在canvas
里运行。1
2
3
4
5
6
7
8
9
10
11
12<!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脚本,形如:
1 | // 继承VRPage父类,开发每一个场景 |
这里参照了类似Unity3d和React的开发模式,在start方法里创建3d模型,在update方法里处理3d动画,这样的好处在于:
- 每一个场景都可以进行独立开发而互不影响;
- 一旦VR环境初始化之后,不需要在每次场景跳转切换时重新初始化一遍。
VRCore.js作为公用模块管理整个webvr应用的所有子场景,包括场景初始化、VR相机渲染、场景切换、场景回收等静态函数。
VRPage.js作为每个场景的工厂类,支持不同3d页面(场景)之间的代码独立。
每一个VR页面的生命周期都是:创建物体->加载模型->启动渲染的过程,因此,需要创建一个基类,来实现每一个VR场景实例的生命周期。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27//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
方法监听场景是否加载完毕,一旦加载完毕,便启动渲染。
WebVR场景首次渲染
主要包括四个步骤
- 新建场景
- 创建VR相机
- 加载场景脚本与资源
- 开启动画渲染
VR环境初始化
1 | function createScene({domContainer=document.body,fov=70,far=4000}) { |
首先是three.js开发三部曲,创建场景、相机、渲染器,接着调用initVR
函数来完成VR场景分屏和陀螺仪控制,WebVR基本开发步骤可以参考。
1 | function initVR() { |
开启动画渲染
1 | // VRCore.js |
这里传入参数动画渲染做了三件事,使用loopID作为整个VR应用的全局变量,记录每一帧动画的更新;更新相机控制器和VR渲染器,
WebVR场景切换
主要包括四个步骤
- 暂停渲染
- 清空当前场景物体
- 请求并加载目标场景脚本与资源
- 重启渲染
暂停动画渲染
1 | function renderStop() { |
回收当前场景
1 | function clearScene() { |
按需加载
切换到下一场景,我们需要请求对应的场景脚本,这里使用webpack2的import函数进行代码分离,当然你也可以使用require.ensure(filename => {require(filename)})
方法。1
import(`page/${fileName}.js`);
最终将清空当前场景与请求加载目标场景功能封装为forward
跳转方法,就可以在页面里直接调用了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 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配置略有不同,这里主要给出webpack的基本配置,具体可参考项目地址。
1 | const path = require('path'); |
这里我们将webvr首个场景src/page/index.js
作为项目打包入口,同时将page目录下的文件也作为单独chunk,配合按需加载来支持场景切换。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22module: {
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
需要用到的素材,如下。1
2//page/page2.js
import ASSET_TEXTURE_SKYBOX from 'assets/texture/360_page2.jpg';
webpack相关的plugin配置如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 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配置
最后是polyfill配置,我们需要引入webvr-polyfill和babel-polyfill来分别支持webvr API和ES6 API,并作为一个页面独立脚本。1
2
3// common/vendor.js
import 'babel-polyfill';
import 'webvr-polyfill';
以上WebVR工程已经基本搭建完毕,欢迎各位提出宝贵意见,后续我们将探索daydream和Oculus在webvr上的开发模式,敬请期待。
最后,献上前几天在google开发者网站上看到的:预测未来,不如创造未来。