摘要:本文旨在介绍如何搭建工程以支持多场景开发。有了公用,我们希望这样开发应用,即一个场景对应一个脚本,形如继承父类,开发每一个场景启动渲染之前,创建场景模型场景资源加载完毕,可执行音频播放等。
本文旨在介绍如何搭建WebVR工程以支持多场景开发。
首先,作为一个基本的前端工程来说,我们需要让代码“工程化”,不仅要提供编译构建、压缩打包功能,还要让每个页面模块化;
延伸到WebVR工程,我们也需要考虑就必须考虑“多页面”模块化,即提供多个场景模块化开发,因为一个完整的WebVR App不仅仅只有一个场景。这里可以参考google的WebVR多场景示例:https://vr.chromeexperiments....
多场景开发,最简单的方式就是,一个场景对应一份html、css、js,多个页面需要多个html,每次页面跳转需要重新进行VR渲染进行初始化。
实际上我们在多场景中,场景初始化只需要执行一次(比如,创建一个场景->创建相机->创建渲染器),我们只需要一个index.html作为入口页面,将VR场景初始化、创建、回收、切换封装成公用组件。
在首次进入场景时进行初始化,在需要场景切换时进行场景回收和按需加载,这样一来,用户切换场景时,不用把时间浪费在等待html和初始化场景上。基于以上思路,本人总结的一套WebVR工程搭建方案,供各位参考。
项目地址:https://github.com/YorkChan94...
Demo:https://yorkchan94.github.io/...
相关技术栈: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
主要目录结构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里运行。
webVR-INDEX
有了公用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方法监听场景是否加载完毕,一旦加载完毕,便启动渲染。
WebVR场景首次渲染主要包括四个步骤
新建场景
创建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渲染器,
WebVR场景切换主要包括四个步骤
暂停渲染
清空当前场景物体
请求并加载目标场景脚本与资源
重启渲染
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开发者网站上看到的:预测未来,不如创造未来。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/83025.html
摘要:在文末,我会附上一个可加载的模型方便学习中文艺术字渲染用原生可以很容易地绘制文字,但是原生提供的文字效果美化功能十分有限。 showImg(https://segmentfault.com/img/bVWYnb?w=900&h=385); WebGL 可以说是 HTML5 技术生态链中最为令人振奋的标准之一,它把 Web 带入了 3D 的时代。 初识 WebGL 先通过几个使用 Web...
摘要:目录如何用提高效率后端掘金经常有人说我应该学一门语言,比如之类,但是却不知道如何入门。本文将通过我是如何开发公司年会抽奖系统的后端掘金需求出现年会将近,而年会抽奖环节必不可少,但是抽奖系统却还没有。 云盘一个个倒下怎么办?无需编码,手把手教你搭建至尊私享云盘 - 工具资源 - 掘金微盘挂了,360倒了,百度云盘也立了Flag。能让我们在云端储存分享文件的服务越来越少了。 买一堆移动硬盘...
阅读 1216·2021-11-23 09:51
阅读 1589·2021-11-16 11:45
阅读 3919·2021-10-09 09:43
阅读 2631·2021-07-22 16:47
阅读 913·2019-08-27 10:55
阅读 3375·2019-08-26 17:40
阅读 3041·2019-08-26 11:39
阅读 3188·2019-08-23 18:39