心语家园 2019-06-25
前两期主要介绍了开发WebVR应用的基本套路,不过开发出来的场景还只是“可远观而不可亵玩”,缺乏交互性,本期将带大家走进VR交互世界,看看WebVR事件是如何开发的。
在可交互的VR世界里,用户不再只是个观察者,而是虚拟世界中一个生命,可以与虚拟世界进行通信。这种通信应该是双向的:虚拟场景能感知你的存在(位置、方向),同时你的输入又对物体产生作用。这里借鉴一个VR游戏场景:
上述共采用了四种交互方式,根据输入设备可分为headset头显交互和gamepad手柄交互,前者通过头显行为触发事件(如2、4),后者通过手柄行为触发事件(如1、3)。
这些交互行为都需要硬件支持(比如陀螺仪和加速计提供方向追踪支持),我们需要通过JavaScript API来获取headset或者gamepad的动态数据。
由于各VR产商的头显和手柄具有差异,因此对于用户交互层面的支持度也参差不齐,以下展示各主流VR平台在头显和手柄方面的在交互上的支持情况。
VR类型 |headset|gamepad
:------------:|:------------:|:------------:
Cardboard|3-DoF|无
Daydream Smartphone VR|3-DoF|3-DoF
Daydream Standalone VR|6-DoF|?-DoF
Gear VR|3-DoF|3-DoF
Oculus Rift|6-DoF|6-DoF
HTC Vive|6-DoF|6-DoF
Microsoft MR|6-DoF|6-DoF
表中的DoF(degree of freedom)就是我们常说的自由度,主要为Orientation自由度和Position自由度两种。
通常在VR体系里,3-DoF指的是VR硬件支持Orientation,6-DoF指的是支持Orientation + Position。
Headset交互根据自由度可分为3-DoF和6-DoF,显然,所有的VR头显都应支持Orientation方向的3-DoF追踪。
实现headset交互,首先是要能看得到虚拟世界,上期WebVR深度剖析中VR渲染是实现headset交互的第一步,我们需要使用WebVR API来获取headset数据。
这里再复习一下:当用户戴着headset扭头或走动时,渲染器在每帧通过VRFrameData的视觉-投影矩阵,动态计算出每个物体的MVP复合矩阵,最后进行顶点和片元绘制。
令人兴奋的是,three.js已经将这个过程封装到了相机和渲染器中,帮我们实现了第一步。
第二步,我们需要让三维场景感知用户(头部)的存在,举个例子,当一个球朝着玩家丢过来的时候,玩家机灵一躲,程序根据球体与玩家的位置判断玩家是否躲闪成功。
这时候,我们还需要用到VRFrameData一个重要属性pose
,通过frameData.pose
返回一个VRPose
对象,可获取headset的传感器信息,比如位置、方向、速度和加速度等。
属性|类型|说明
:------------:|:------------:|:------------:
position|Float32Array|返回headset的位置矩阵
orientation|Float32Array|返回headset的方向矩阵
angularAcceleration|Float32Array|返回x, y, z轴每秒的角加速度
angularVelocity |Float32Array|返回x, y, z轴每秒的角速度
linearAcceleration|Float32Array|返回x, y, z轴每秒的线性加速度
linearVelocity |Float32Array|返回x, y, z轴的线性速度
通过这几个属性,我们可以让相机具备物理数据,拥有实体感知能力,而不单单只是观察者模式。
比如,下面实现用户在使用HTC Vive此类6-DoF的headset时,走动超过范围弹出警告的功能:
update() { const { vrdisplay, frameData, userModel } = this; frameData = vrdisplay.getFrameData(frameData); const vrpose = frameData.pose; userModel.position.fromArray(vrpose.position); // 将VRPose位置矩阵赋予用户角色 const { x, y, z } = userModel.position; // 解构用户位置的x,y,z坐标 if ( Math.abs(x) > 20 || Math.abs(y) > 20 || Math.abs(z) > 20 ) { // 当用户离初始点超过20×20×20的空间时,弹出警告 showWarningToast(); // 展示警告框 } }
同样,three.js在VR模式下会自动将VRPose的position
和orientation
转化成camera的Object3D属性,因此我们可以直接调用camera.position
和camera.quaternation/rotation
获取用户的位置和朝向,代码简化如下:
update() { const { camera, userModel } = this; userModel.position.copy(camera.position); const { x, y, z } = userModel.position; // 解构用户位置坐标 if ( Math.abs(x) > 20 || Math.abs(y) > 20 || Math.abs(z) > 20 ) { showWarningToast(); // 用户离初始点超过20×20×20的空间时,弹出警告框 } }
基本的headset交互事件就是这样,理解了这些,我们可以实现gaze事件监听,点头摇头事件监听等。
除了headset,现在大部分VR还搭配gamepad,用户通过手持手柄可以与虚拟场景进行交互。
对于gamepad手柄而言,也有3-DoF和6-DoF的两种类型:
相比headset传感器输入产生的交互,gamepad还多了各种输入元件,如按钮、touchpad触控板或thumbstick手摇杆等。
于是,根据手柄输入硬件又可将gamepad事件分为三类:
A. 传感器事件:由传感器对手柄进行物理追踪,如激光笔交互;
B. 按钮事件:通过点击按钮产生的交互行为;
C. 控制单元事件:由thumbstick, touchpad输入产生,如swipe滑动来翻页等。
那么如何实现gamepad的交互事件呢?其实换个问题就是:如何访问手柄的硬件信息,答案是使用Gamepad API,详见WebVR开发教程——交互事件(二)使用Gamepad
WebVR开发教程——深度剖析 关于WebVR的开发调试方案以及原理机制
WebVR开发教程——标准入门 使用Three.js开发WebVR场景的入门教程