【腾讯Bugly干货分享】WebVR如此近-three.js的WebVR示例解析_Golang语言社区的博客-程序员秘密

关于WebVR
最近VR的发展十分吸引人们的眼球,很多同学应该也心痒痒的想体验VR设备,然而现在的专业硬件价格还比较高,入手一个估计就要吃土了。但是,对于我们前端开发者来说,我们不仅可以简单地在手机上进行视觉上的VR体验,还可以立马上手进行Web端VR应用的开发!

WebVR是一个实验性的Javascript API,允许HMD(head-mounted displays)连接到web apps,同时能够接受这些设备的位置和动作信息。这让使用Javascript开发VR应用成为可能(当然已经有很多接口API让Javascript作为开发语言了,不过这并不影响我们为WebVR感到兴奋)。而让我们能够立马进行预览与体验,移动设备上的chrome已经支持了WebVR并使手机作为一个简易的HMD。手机可以把屏幕分成左右眼视觉并应用手机中的加速度计、陀螺仪等感应器,你需要做的或许就只是买一个cardboard。
不说了,我去下单了!

img cardborad纸盒,一顿食堂饭钱即可入手
前言
WebVR仍处于w3c的草案阶段,所以开发和体验都需要polyfill。
这篇解析基于 webvr-boilerplate ,这个示例的作者,任职google的 Boris Smus 同时也编写了 webvr-polyfill 。 three.js examples中也提供了关于VR的控制例子。这里主要通过对代码注释的方式来解读关键的文件。
示例的最终效果如下,打开Demo并把手机放进cardboard即可体验。你也可以在我的github对照有关的代码和注释。
  1. Demo链接:http://soaanyip.github.io/webvr-boilerplate-cn/ 
  2. 我的github:https://github.com/SoAanyip/WebVR-Boilerplate-CN/
复制代码
 
按照惯例,这篇解析默认你至少有three.js相关的基础知识。有兴趣也可以浏览一下我之前写的
  1. 《ThreeJS 轻松实现主视觉太阳系漫游》 
  2. https://zhuanlan.zhihu.com/p/20796329
复制代码
这篇解析中three.js的版本为V76。文中如有各种错误请指出!
先从html开始
在示例中只用到了一个index.html。首先meta标签有几个值得注意的:
  1. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
  2. <meta name="mobile-web-app-capable" content="yes">
  3. <meta name="apple-mobile-web-app-capable" content="yes" />
  4. <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
复制代码
这几个标签对web app开发的同学来说应该是十分熟悉了。其中 shrink-to-fit=no 是Safari的特性,禁止页面通过缩放去适应适口。
接下来在js引用的部分,引用了这几个资源:
  1. //作者引入的一个promise polyfill;
  2. <script src="node_modules/es6-promise/dist/es6-promise.js"></script>
复制代码
  1. //three.js核心库
  2. <script src="node_modules/three/three.js"></script>
复制代码
  1. //从连接的VR设备中获得位置信息并应用在camera对象上,将在下文展开;
  2. <script src="node_modules/three/examples/js/controls/VRControls.js"></script>
复制代码
  1. //处理立体视觉和绘制相关,将在下文展开;
  2. <script src="node_modules/three/examples/js/effects/VREffect.js"></script>
复制代码
  1. //WebVR polyfill,下文简述调用的API option;
  2. <script src="node_modules/webvr-polyfill/build/webvr-polyfill.js"></script>
复制代码
  1. // 界面按钮以及进入/退出VR模式的控制等。
  2. <script src="build/webvr-manager.js"></script>
复制代码
具体的整个项目文件,可以在这里查看有关的代码和注释。
VRControls.js - HMD状态感应
这个文件主要对HMD的状态信息进行获取并应用到camera上。例如在手机上显示的时候,手机的旋转倾斜等就会直接作用到camera上。

第一步是获取连接的VR设备,这一步是基本通过WebVR的API进行的:
  1. //获取VR设备(作为信息输入源。如有多个则只取第一个)
  2. function gotVRDevices( devices ) {
  3.     for ( var i = 0; i < devices.length; i ++ ) {
  4.         if ( ( 'VRDisplay' in window && devices[ i ] instanceof VRDisplay ) || ( 'PositionSensorVRDevice' in window && devices[ i ] instanceof PositionSensorVRDevice ) ) {
  5.             vrInput = devices[ i ];
  6.             break;  // We keep the first we encounter
  7.         }
  8.     }

  9.     if ( !vrInput ) {
  10.         if ( onError ) onError( 'VR input not available.' );
  11.     }
  12. }
  13. //调用WebVR API获取VR设备
  14. if ( navigator.getVRDisplays ) {
  15.     navigator.getVRDisplays().then( gotVRDevices );
  16. } else if ( navigator.getVRDevices ) {
  17.     // Deprecated API.
  18.     navigator.getVRDevices().then( gotVRDevices );
  19. }
复制代码
然后是三个关于位置的参数:
  1. // the Rift SDK returns the position in meters
  2. // this scale factor allows the user to define how meters
  3. // are converted to scene units.
  4. //Rift SDK返回的位置信息是以米作为单位的。这里可以定义以几倍的缩放比例转换为three.js中的长度。
  5. this.scale = 1;

  6. // If true will use "standing space" coordinate system where y=0 is the
  7. // floor and x=0, z=0 is the center of the room.
  8. //表示使用者是否站立姿态。当为false时camra会在y=0的位置,而为true时会结合下面的模拟身高来决定camera的y值。
  9. //在无法获取用户姿势信息的设备上,需要在调用时直接指定是站姿还是坐姿。
  10. this.standing = false;

  11. // Distance from the users eyes to the floor in meters. Used when
  12. // standing=true but the VRDisplay doesn't provide stageParameters.
  13. //当为站立姿态时,用户的眼睛(camera)的高度(跟如有硬件时返回的单位一致,为米)。这里会受scale的影响。如scale为2时,实际camera的高度就是3.2。
  14. this.userHeight = 1.6;
复制代码
通过WebVR API获取到用户的设备信息,并应用到camera上,是一个持续进行的过程。因此这部分的信息更新会在requestAnimationFrame中不断地调用。
  1. //将在requestAnimationFrame中应用更新
  2. this.update = function () {
  3.     if ( vrInput ) {
  4.         if ( vrInput.getPose ) {
  5.             //方法返回传感器在某一时刻的信息(object)。例如包括时间戳、位置(x,y,z)、线速度、线加速度、角速度、角加速度、方向信息。
  6.             var pose = vrInput.getPose();
  7.             //orientation 方向
  8.             if ( pose.orientation !== null ) {
  9.                 //quaternion  四元数
  10.                 //把设备的方向复制给camera
  11.                 object.quaternion.fromArray( pose.orientation );
  12.             }
  13.             //位置信息
  14.             if ( pose.position !== null ) {
  15.                 //同样把设备的位置复制给camera
  16.                 object.position.fromArray( pose.position );
  17.             } else {
  18.                 object.position.set( 0, 0, 0 );
  19.             }

  20.         } else {
  21.             // Deprecated API.
  22.             var state = vrInput.getState();
  23.             if ( state.orientation !== null ) {
  24.                 object.quaternion.copy( state.orientation );
  25.             }
  26.             if ( state.position !== null ) {
  27.                 object.position.copy( state.position );
  28.             } else {
  29.                 object.position.set( 0, 0, 0 );
  30.             }
  31.         }

  32.         //TODO 此块会一直执行
  33.         if ( this.standing ) {
  34.             //如果硬件返回场景信息,则应用硬件返回的数据来进行站姿转换
  35.             if ( vrInput.stageParameters ) {
  36.                 object.updateMatrix();
  37.                 //sittingToStandingTransform返回一个Matrix4,表示从坐姿到站姿的变换。
  38.                 standingMatrix.fromArray(vrInput.stageParameters.sittingToStandingTransform);
  39.                 //应用变换到camera。
  40.                 object.applyMatrix( standingMatrix );
  41.             } else {
  42.                 //如果vrInput不提供y高度信息的话使用userHeight作为高度
  43.                 object.position.setY( object.position.y + this.userHeight );
  44.             }

  45.         }
  46.         //使用上面定义的this.scale来缩放camera的位置。
  47.         object.position.multiplyScalar( scope.scale );
  48.     }
  49. };
复制代码
以上是vrcontrols的关键代码。
VREffect.js - 立体视觉
VREffect.js主要把屏幕显示切割为左右眼所视的屏幕,两个屏幕所显示的内容具有一定的差异,使得人的双目立体视觉可以把屏幕中的内容看得立体化。这个文件主要的流程如下图:

首先是对画布大小进行了设定。其中 renderer.setPixelRatio( 1 ); 是防止在retina等屏幕上出现图像变形等显示问题。
  1. //初始化或者resize的时候进行。
  2. this.setSize = function ( width, height ) {
  3.     rendererSize = { width: width, height: height };

  4.     //是否VR模式中
  5.     if ( isPresenting ) {
  6.         //getEyeParameters包含了渲染某个眼睛所视的屏幕的信息,例如offset,FOV等
  7.         var eyeParamsL = vrHMD.getEyeParameters( 'left' );
  8.         //设备像素比
  9.         //若设备像素比不为1时会出现显示问题。
  10.         //https://github.com/mrdoob/three.js/pull/6248
  11.         renderer.setPixelRatio( 1 );

  12.         if ( isDeprecatedAPI ) {
  13.             renderer.setSize( eyeParamsL.renderRect.width * 2, eyeParamsL.renderRect.height, false );

  14.         } else {
  15.             renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false );
  16.         }

  17.     } else {
  18.         renderer.setPixelRatio( rendererPixelRatio );
  19.         renderer.setSize( width, height );
  20.     }
  21. };
复制代码
然后是关于全屏模式的设置,这里跟上面的设定差不远:
  1. //显示设备进入全屏显示模式
  2. function onFullscreenChange () {
  3.     var wasPresenting = isPresenting;
  4.     isPresenting = vrHMD !== undefined && ( vrHMD.isPresenting || ( isDeprecatedAPI && document[ fullscreenElement ] instanceof window.HTMLElement ) );
  5.     if ( wasPresenting === isPresenting ) {
  6.         return;
  7.     }

  8.     //如果此次事件是进入VR模式
  9.     if ( isPresenting ) {
  10.         rendererPixelRatio = renderer.getPixelRatio();
  11.         rendererSize = renderer.getSize();

  12.         //getEyeParameters包含了渲染某个眼睛所视的屏幕的信息,例如offset,FOV等
  13.         var eyeParamsL = vrHMD.getEyeParameters( 'left' );
  14.         var eyeWidth, eyeHeight;

  15.         if ( isDeprecatedAPI ) {
  16.             eyeWidth = eyeParamsL.renderRect.width;
  17.             eyeHeight = eyeParamsL.renderRect.height;
  18.         } else {
  19.             eyeWidth = eyeParamsL.renderWidth;
  20.             eyeHeight = eyeParamsL.renderHeight;
  21.         }
  22.         renderer.setPixelRatio( 1 );
  23.         renderer.setSize( eyeWidth * 2, eyeHeight, false );

  24.     } else {
  25.         renderer.setPixelRatio( rendererPixelRatio );
  26.         renderer.setSize( rendererSize.width, rendererSize.height );
  27.     }
  28. }
复制代码
接下来是对表示左右眼的camera的设定。两个camera也肯定是PerspectiveCamera:
  1. var cameraL = new THREE.PerspectiveCamera();
  2. //左camera显示layer 1层(即当某个元素只出现在layer 1时,只有cameraL可见。)
  3. cameraL.layers.enable( 1 );

  4. var cameraR = new THREE.PerspectiveCamera();
  5. cameraR.layers.enable( 2 );
复制代码
从WebVR API中获取关于某个眼睛所视的屏幕的信息:
  1. //getEyeParameters包含了渲染某个眼睛所视的屏幕的信息,例如offset,FOV等
  2. var eyeParamsL = vrHMD.getEyeParameters( 'left' );
  3. var eyeParamsR = vrHMD.getEyeParameters( 'right' );

  4. if ( ! isDeprecatedAPI ) {
  5.     // represents the offset from the center point between the user's eyes to the center of the eye, measured in meters.
  6.     //瞳距的偏移
  7.     eyeTranslationL.fromArray( eyeParamsL.offset );
  8.     eyeTranslationR.fromArray( eyeParamsR.offset );
  9.     //represents a field of view defined by 4 different degree values describing the view from a center point.
  10.     //获得左右眼的FOV
  11.     eyeFOVL = eyeParamsL.fieldOfView;
  12.     eyeFOVR = eyeParamsR.fieldOfView;

  13. } else {
  14.     eyeTranslationL.copy( eyeParamsL.eyeTranslation );
  15.     eyeTranslationR.copy( eyeParamsR.eyeTranslation );
  16.     eyeFOVL = eyeParamsL.recommendedFieldOfView;
  17.     eyeFOVR = eyeParamsR.recommendedFieldOfView;
  18. }

  19. if ( Array.isArray( scene ) ) {
  20.     console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' );
  21.     scene = scene[ 0 ];
  22. }
复制代码
由于左右camera的视锥体还没确定,需要对获得的FOV信息进行计算来确定。在涉及透视投影矩阵的部分会比较复杂,所以这里不展开来说。如果有错误请指出:
  1. cameraL.projectionMatrix = fovToProjection( eyeFOVL, true, camera.near, camera.far );
  2. cameraR.projectionMatrix = fovToProjection( eyeFOVR, true, camera.near, camera.far );

  3. //角度弧度的转换,然后进行后续的计算
  4. function fovToProjection( fov, rightHanded, zNear, zFar ) {
  5.     //角度转换为弧度  如30度转为1/6 PI
  6.     var DEG2RAD = Math.PI / 180.0;

  7.     var fovPort = {
  8.         upTan: Math.tan( fov.upDegrees * DEG2RAD ),
  9.         downTan: Math.tan( fov.downDegrees * DEG2RAD ),
  10.         leftTan: Math.tan( fov.leftDegrees * DEG2RAD ),
  11.         rightTan: Math.tan( fov.rightDegrees * DEG2RAD )
  12.     };

  13.     return fovPortToProjection( fovPort, rightHanded, zNear, zFar );
  14. }

  15. //根据从设备获得的FOV以及相机设定的near、far来生成透视投影矩阵
  16. function fovPortToProjection( fov, rightHanded, zNear, zFar ) {

  17.     //使用右手坐标
  18.     rightHanded = rightHanded === undefined ? true : rightHanded;
  19.     zNear = zNear === undefined ? 0.01 : zNear;
  20.     zFar = zFar === undefined ? 10000.0 : zFar;

  21.     var handednessScale = rightHanded ? - 1.0 : 1.0;

  22.     // start with an identity matrix

  23.     var mobj = new THREE.Matrix4();
  24.     var m = mobj.elements;

  25.     // and with scale/offset info for normalized device coords
  26.     var scaleAndOffset = fovToNDCScaleOffset( fov );

  27.     //建立透视投影矩阵

  28.     // X result, map clip edges to [-w,+w]
  29.     m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ];
  30.     m[ 0 * 4 + 1 ] = 0.0;
  31.     m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale;
  32.     m[ 0 * 4 + 3 ] = 0.0;

  33.     // Y result, map clip edges to [-w,+w]
  34.     // Y offset is negated because this proj matrix transforms from world coords with Y=up,
  35.     // but the NDC scaling has Y=down (thanks D3D?)
  36.     //NDC(归一化设备坐标系)是左手坐标系
  37.     m[ 1 * 4 + 0 ] = 0.0;
  38.     m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ];
  39.     m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale;
  40.     m[ 1 * 4 + 3 ] = 0.0;

  41.     // Z result (up to the app)
  42.     m[ 2 * 4 + 0 ] = 0.0;
  43.     m[ 2 * 4 + 1 ] = 0.0;
  44.     m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale;
  45.     m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar );

  46.     // W result (= Z in)
  47.     m[ 3 * 4 + 0 ] = 0.0;
  48.     m[ 3 * 4 + 1 ] = 0.0;
  49.     m[ 3 * 4 + 2 ] = handednessScale;
  50.     m[ 3 * 4 + 3 ] = 0.0;

  51.     //转置矩阵,因为mobj.elements是column-major的
  52.     mobj.transpose();

  53.     return mobj;
  54. }

  55. //计算线性插值信息
  56. function fovToNDCScaleOffset( fov ) {

  57.     var pxscale = 2.0 / ( fov.leftTan + fov.rightTan );
  58.     var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5;
  59.     var pyscale = 2.0 / ( fov.upTan + fov.downTan );
  60.     var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5;
  61.     return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] };
  62. }
复制代码
之后是确定左右camera的位置和方向。由于左右眼(左右camera)肯定是在头部(主camera,位置和方向由HMD返回的信息确定)上的,在我们获得把眼睛从头部飞出去的超能力之前,左右camera的位置和方向都是根据主camera来设定的。
  1. //使主camera的位移、旋转、缩放变换分解,作用到左camra 右camera上。
  2. camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale );
  3. camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale );

  4. var scale = this.scale;
  5. //左右眼camera根据瞳距进行位移。
  6. cameraL.translateOnAxis( eyeTranslationL, scale );
  7. cameraR.translateOnAxis( eyeTranslationR, scale );
复制代码
最后便是对两个区域进行渲染。
  1. // 渲染左眼视觉
  2. renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
  3. renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
  4. renderer.render( scene, cameraL );

  5. // 渲染右眼视觉
  6. renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
  7. renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
  8. renderer.render( scene, cameraR );
复制代码
VREffect文件的关键点差不多是上述这些。
webvr-polyfill.js - 让现在使用WebVR成为可能
webvr-polyfill.js 根据WebVR API的草案来实现了一套polyfill。例如根据所处环境是pc还是手机来确定使用的是 CardboardVRDisplay 还是 MouseKeyboardVRDisplay ,在手机环境下的话使用 Device API 来处理手机旋转、方向等参数的获取。此外作者还顺便做了几个提示图标和画面来优化体验。在这里我们来看一下其API参数:
  1. WebVRConfig = {
  2.   /**
  3.    * webvr-polyfill configuration
  4.    */

  5.   // Flag to disabled the UI in VR Mode.
  6.   //是否禁用VR模式的UI。
  7.   CARDBOARD_UI_DISABLED: false, // Default: false

  8.   // Forces availability of VR mode.
  9.   //是否强制使VR模式可用。
  10.   //FORCE_ENABLE_VR: true, // Default: false.

  11.   // Complementary filter coefficient. 0 for accelerometer, 1 for gyro.
  12.   //互补滤波系数。加速度计在静止的时候是很准的,但运动时的角度噪声很大,陀螺仪反之。
  13.   //互补滤波器徘徊在信任陀螺仪和加速度计的边界。首先选择一个时间常数,然后用它来计算滤波器系数。
  14.   //例如陀螺仪的漂移是每秒2度,则可能需要一个少于一秒的时间常数去保证在每一个方向上的漂移不会超过2度。
  15.   //但是当时间常数越低,越多加速度计的噪声将允许通过。所以这是一个权衡的内容。
  16.   //K_FILTER: 0.98, // Default: 0.98.

  17.   // Flag to disable the instructions to rotate your device.
  18.   //是否禁用旋转设备的提示(横放手机以进入全屏)。
  19.   ROTATE_INSTRUCTIONS_DISABLED: false, // Default: false

  20.   // How far into the future to predict during fast motion.
  21.   //由于有给定的方向以及陀螺仪信息,选择允许预测多长时间之内的设备方向,在设备快速移动的情况下可以让渲染比较流畅。
  22.   //PREDICTION_TIME_S: 0.040, // Default: 0.040 (in seconds).

  23.   // Flag to disable touch panner. In case you have your own touch controls、
  24.   //是否禁用提供的触摸控制,当你有自己的触摸控制方式时可以禁用
  25.   //TOUCH_PANNER_DISABLED: true, // Default: false.

  26.   // To disable keyboard and mouse controls, if you want to use your own
  27.   // implementation.
  28.   //是否禁用pc下的鼠标、键盘控制。同上。
  29.   //MOUSE_KEYBOARD_CONTROLS_DISABLED: true, // Default: false.

  30.   // Enable yaw panning only, disabling roll and pitch. This can be useful for
  31.   // panoramas with nothing interesting above or below.
  32.   // 仅关心左右角度变化,忽略上下和倾斜等。
  33.   // YAW_ONLY: true, // Default: false.

  34.   // Prevent the polyfill from initializing immediately. Requires the app
  35.   // to call InitializeWebVRPolyfill() before it can be used.
  36.   //是否阻止组件直接进行初始化构建。如果为true则需要自己调用InitializeWebVRPolyfill()。
  37.   //DEFER_INITIALIZATION: true, // Default: false.

  38.   // Enable the deprecated version of the API (navigator.getVRDevices).
  39.   //允许使用过时版本的API。
  40.   //ENABLE_DEPRECATED_API: true, // Default: false.

  41.   // Scales the recommended buffer size reported by WebVR, which can improve
  42.   // performance. Making this very small can lower the effective resolution of
  43.   // your scene.
  44.   //在VR显示模式下对WebVR推荐的屏幕比例进行缩放。在IOS下如果不为0.5会出现显示问题,查看
  45.   //https://github.com/borismus/webvr-polyfill/pull/106
  46.   BUFFER_SCALE: 0.5, // default: 1.0

  47.   // Allow VRDisplay.submitFrame to change gl bindings, which is more
  48.   // efficient if the application code will re-bind it's resources on the
  49.   // next frame anyway.
  50.   // Dirty bindings include: gl.FRAMEBUFFER_BINDING, gl.CURRENT_PROGRAM,
  51.   // gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING,
  52.   // and gl.TEXTURE_BINDING_2D for texture unit 0
  53.   // Warning: enabling this might lead to rendering issues.
  54.   //允许 VRDisplay.submitFrame使用脏矩形渲染。但是开启此特性可能会出现渲染问题。
  55.   //DIRTY_SUBMIT_FRAME_BINDINGS: true // default: false
  56. };
复制代码
其config主要是对一些用户可选项进行设定。在文件内部,更多的是对 Device API 的应用等等。
现在就开始编写WebVR应用吧!
在示例的最后是一个显示简单的旋转立方体的demo。此处可以帮助我们学习怎么创建一个WebVR应用。
首先是建立好scene、renderer、camera的三要素:
  1. // Setup three.js WebGL renderer. Note: Antialiasing is a big performance hit.
  2. // Only enable it if you actually need to.
  3. var renderer = new THREE.WebGLRenderer({antialias: true});
  4. renderer.setPixelRatio(window.devicePixelRatio);

  5. // Append the canvas element created by the renderer to document body element.
  6. document.body.appendChild(renderer.domElement);

  7. // Create a three.js scene.
  8. var scene = new THREE.Scene();

  9. // Create a three.js camera.
  10. var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
复制代码
对上面解析过的controls、effect进行调用:
  1. // Apply VR headset positional data to camera.
  2. var controls = new THREE.VRControls(camera);
  3. //站立姿态
  4. controls.standing = true;

  5. // Apply VR stereo rendering to renderer.
  6. var effect = new THREE.VREffect(renderer);
  7. effect.setSize(window.innerWidth, window.innerHeight);

  8. // Create a VR manager helper to enter and exit VR mode.
  9. //按钮和全屏模式管理
  10. var params = {
  11.   hideButton: false, // Default: false.
  12.   isUndistorted: false // Default: false.
  13. };
  14. var manager = new WebVRManager(renderer, effect, params);
复制代码
在场景中,添加一个网格显示的空间,在空间内加入一个小立方体:
  1. // Add a repeating grid as a skybox.
  2. var boxSize = 5;
  3. var loader = new THREE.TextureLoader();
  4. loader.load('img/box.png', onTextureLoaded);

  5. function onTextureLoaded(texture) {
  6.   texture.wrapS = THREE.RepeatWrapping;
  7.   texture.wrapT = THREE.RepeatWrapping;
  8.   texture.repeat.set(boxSize, boxSize);

  9.   var geometry = new THREE.BoxGeometry(boxSize, boxSize, boxSize);
  10.   var material = new THREE.MeshBasicMaterial({
  11.     map: texture,
  12.     color: 0x01BE00,
  13.     side: THREE.BackSide
  14.   });

  15.   // Align the skybox to the floor (which is at y=0).
  16.   skybox = new THREE.Mesh(geometry, material);
  17.   skybox.position.y = boxSize/2;
  18.   scene.add(skybox);

  19.   // For high end VR devices like Vive and Oculus, take into account the stage
  20.   // parameters provided.
  21.   //在高端的设备上,要考虑到设备提供的场景信息的更新。
  22.   setupStage();
  23. }

  24. // Create 3D objects.
  25. var geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
  26. var material = new THREE.MeshNormalMaterial();
  27. var cube = new THREE.Mesh(geometry, material);

  28. // Position cube mesh to be right in front of you.
  29. cube.position.set(0, controls.userHeight, -1);

  30. scene.add(cube);
复制代码
最后便是设置requestAnimationFrame的更新。在animate的函数中,不但要考虑立方体的旋转问题,更重要的是要不断地获取HMD返回的信息以及对camera进行更新。
  1. // Request animation frame loop function
  2. var lastRender = 0;
  3. function animate(timestamp) {
  4.   var delta = Math.min(timestamp - lastRender, 500);
  5.   lastRender = timestamp;

  6.   //立方体的旋转
  7.   cube.rotation.y += delta * 0.0006;

  8.   // Update VR headset position and apply to camera.
  9.   //更新获取HMD的信息
  10.   controls.update();

  11.   // Render the scene through the manager.
  12.   //进行camera更新和场景绘制
  13.   manager.render(scene, camera, timestamp);

  14.   requestAnimationFrame(animate);
  15. }
复制代码
总结
以上便是此示例的各个文件的解析。我相信VR的形式除了在游戏上的应用的前景,在其他方面也有其值得探索的可行性。所以让我们一起来开始WebVR之旅吧!
参考内容

7.jpg (76.34 KB, 下载次数: 0)

7.jpg
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/QQ1528884535/article/details/78140697

智能推荐

一个超牛的PowerDesigner的破解 ..._zxl333的博客-程序员秘密

从官网下载下来的PowerDesigner 好像只有十五天的试用期.在网上搜了好几个补丁都没有用.后来搜到一个超牛的破解方法.破解方法如下: 在PowerDesigner安装目录下,找到pdflm12.dll,用记事本或其它编辑工具打开,显示的应该是一些二进制的内容.找到83 C4 14 8B 85 E4 FE FF FF,把这一段改成:83 C4 14 33 C0 90 90 90 90.重新打

Setinterl全面介绍_weixin_33895604的博客-程序员秘密

Setinterval全面介绍源文件:From: &amp;lt;由 Windows Internet Explorer 7 保存&amp;gt;Subject: =?gb2312?B?c2V0SW50ZXJ2YWzIq8PmtcS96cnc?=Date: Tue, 3 Jun 2008 11:10:01 +0800MIME-Version: 1.0Content-Type...

matlab 16qam误码率图,16QAM理论误码率与实际误码率MATLAB仿真程序(最新整理)_御坂1995的博客-程序员秘密

《16QAM理论误码率与实际误码率MATLAB仿真程序(最新整理)》由会员分享,可在线阅读,更多相关《16QAM理论误码率与实际误码率MATLAB仿真程序(最新整理)(5页珍藏版)》请在人人文库网上搜索。1、16QAM%等效基带调制clc;clear all;close all;%关闭所有窗口,比如之前画的图nsymbol = 10000;%每种信噪比下符号数的发送符号数,106 个数errorn...

docker\docker-compose安装_卢舍那的博客-程序员秘密

docker安装1、先卸载之前的 yum list installed |grep docker yum remove docker-ce docker-ce-cli containerd2、安装显示支持的版本:yum list docker --showduplicates | sort -r安装:yum install docker-ce-17.06.2.ce-1.el7.centos -y3、启动systemctl start dockerdocker-compose安装1、

网络:W5500用浏览器配置设备_weixin_30517001的博客-程序员秘密

1.背景嵌入式端使用网络通信后,可以在PC端进行设备配置。方法有二:1)上位机配置;2)浏览器配置。上位机配置可以把设置和测量作为一体,功能可以很强大,体验较好。浏览器配置就是在电路板上搭载一个嵌入式的web服务器,所以功能一般的很有限。特定情况下,搭载一个浏览器设置接口,可以大大方便设备的使用,毕竟是个电脑就肯定有浏览器的。2.W5500浏览器配置例程分析1)界面如...

随便推点

基于docker部署mysql主从复制集群_CRUD的W的博客-程序员秘密

目录前言部署mysql1、先直接启动一个mysql容器2、把配置文件拷贝出来,因为我们主从架构需要用到配置文件,使用考虑使用挂载的方式修改配置文件3、删除我们刚才的容器,我们仅仅是为了拿到配置文件4、修改配置文件内容5、启动master和slave节点6、进入master容器内7、获取binlog名字和position8、开启主从同步9、测试是否主从配置成功总结前言本文介绍如何基于docker部署一主一从的mysql集群部署mysql首先我们先启动两个mysql容器,安装docker的步骤在此不做介

在selenium中,find_element与find_elements的区别_study forever的博客-程序员秘密

在selenium中,find_element与find_elements的区别find_element只返回查找到的第一个元素,如果没有查找到元素,则返回NoSuchElementException的异常find_elements返回一个包含所有元素的列表,如果没有元素,则返回空列表...

黑马程序员—集合(下)-Map_w13774591947的博客-程序员秘密

----------android培训、java培训、期待与您交流!----------

SP2-0667: Message file sp1<lang>.msb not found sqlplus oracle_金士顿的博客-程序员秘密

up vote0 down vote favorite1I've a linux RedHat which contains oracle database. I setted all environnement variables, but when I runningsqlplus command I've this error : SP2-0667

swingbench运行在linux里面,Swingbench安装前的准备工作(代码)_李妙文-赵可心的博客-程序员秘密

1.确认安装的平台与java,oracle相关配置[email protected]:~&gt; cat /etc/issue #查看系统版本3.4.Welcome to SUSE Linux Enterprise Server 10 SP3 (x86_64) - Kernel \r (\l)[email protected]:~&gt; java -version #使用这个方式即可确认jave当前的版本...

计算机毕业设计-ssm餐厅点餐系统源码-餐厅选座管理系统代码-餐厅订餐管理系统javaweb项目_QQ_2105837402的博客-程序员秘密

计算机毕业设计-ssm餐厅点餐系统源码-餐厅选座管理系统代码-餐厅订餐管理系统javaweb项目注意:该项目只展示部分功能,如需了解,评论区咨询即可。作者:IT跃迁谷1.开发环境开发语言:Java后台框架:SSM(Spring+SpringMVC+Mybatis)数据库:MySQL架构:B/S源码类型: Web编译工具:Idea、Eclipse、MyEclipse (选其一)其他:jdk1.8、Tomcat8.0 、Maven2.系统的设计与实现2.1 用户类型用户角色分为