WebVR开发——使用Gamepad API

14 天前

WebVR场景渲染发离不开WebVR API,但除了渲染一个双屏全景是远远不够的,因为这样的场景“可远观二不可亵玩”。随着大部分VR平台提供了VR游戏手柄,如今用户可以对VR物体进行操纵了,本文将探讨如何使用手柄实现我们的WebVR应用。

GamePad手柄交互

现在大部分VR平台都搭配VR手柄支持,用户通过手持手柄可以与虚拟场景进行交互。

对于gamepad手柄而言,可分为3-DoF和6-DoF的两种类型:

  • 3-DoF如daydream controller,只支持方向追踪,于是google推荐采用laser激光笔进行交互。
  • 6-DoF如Oculus touch,可以进行方向和位置追踪,因此可以很好的模拟手臂的动作。

gamepad还多了各种输入元件,如按钮、touchpad触控板或thumbstick手摇杆等。

于是,根据手柄输入硬件又可将gamepad事件分为三类:

A. 传感器事件:由传感器对手柄进行物理追踪,如激光笔交互;

B. 按钮事件:通过点击按钮产生的交互行为;

C. 控制单元事件:由thumbstick, touchpad输入产生,如swipe滑动来翻页等。

那么如何实现gamepad的交互事件呢?其实换个问题就是:如何访问手柄的硬件信息,答案是使用Gamepad API

Gamepad API

Gamepad API是一个HTML5接口,让开发者可以通过js访问游戏手柄,使用Gamepad API的第一步是获取gamepad实例。

典型的Gamepad构造 一个典型的gamepad一般都会有button按钮和axes control控制单元,而VR gamepad则是在前两者的基础上,加上对传感器的支持。

Gamepad

属性 说明
id string类型,包含手柄的标识信息。
connected bool类型,反映手柄是否处于连接状态
buttons 返回GampadButton对象数组,即手柄上的所有可用按钮
axes 返回double类型数组,数组元素为手柄控制元件上各轴向数值
pose 返回一个GamepadPose对象,包含手柄的方向和位置信息

获取headset实例需要调用navigator.getVRDisplays()方法,同样,获取一个手柄的实例,则是调用navigator.getGamepads()方法,它返回一个gamepads数组。 一旦有手柄连接上,gamepads数组将产生有效的gamepad对象,否则,只能是null。

function getGamepad(id)
  const gamepads = navigator.getGamepads();
  for (let i = 0; i < gamepads.length; ++i) {
    let gamepad = gamepads[i];
    // 只有gamepad不为null才有效
    if (gamepad && gamepad.id === id)  return gamepad;
  }
}
// 或者写成这样: let getGamepad = id => navigator.getGamepads().filter( gamepad => gamepad && gamepad.id === id )[0];
this.gamepad = getGamepad('daydream vr controller'); // 获取daydream controller手柄

上面实现的是根据手柄id获取单个gamepad实例的方法,有些VR手柄如Vive Controller, Oculus Touch等是双手柄,则需要获取两个gamepad实例。

接下来,我将针对gamepad实例的buttons, axes, pose三个重要属性进行介绍,它们对应的是手柄按钮、控制元件、传感器三类组件,是实现gamepad交互事件的三大法宝。

Gamepad.buttons

Gamepad.buttons作为gamepad实例的一个重要属性,代表手柄或遥控器上的所有可用按钮,返回的是由一个或多个GamepadButton对象组成的数组。

GamepadButton顾名思义指的是gamepad上的按钮实例,我们可以该实例获取按钮的状态,比如是否被点击。 Gamepad Buttons

属性 类型 说明
id string类型 按钮的id名
pressed bool类型 按钮是否处于按压状态。
touched bool类型 按钮是否处于触摸状态。
value double类型 反映按钮被按压的程度

由于gamepad的构造都不尽相同,如果想识别Gamepad.buttons中确认键或者返回键对象,可以通过GamepadButton.id的值来判断。 下面是利用pressed实现tap事件的代码,这里定义的tap事件,是指手指按下按钮瞬间产生的触发事件,不按压或持续按压过程不会产生tap。

update() {
  const button = this.gamepad.buttons[0]; // 确认键对象通常位于数组第一个
  if (!this._lastPressed && button.pressed) {
    // 处理tap事件
  }
  this._lastPressed = button.pressed;
}

用代码的语言来说,就是只有满足:1) 上一帧的button.pressedfalse; 2) 当前帧的button.pressedtrue的才会触发tap事件。 于是,我们需要定义一个_lastPressed来记录上一帧button是否pressed。 使用gamepad.buttons可以轻松实现gamepad按钮的点击事件,接下来,将介绍另一个重要属性gampad.axes,通过它我们可以判断触控板手势、摇杆朝向等。

Gamepad.axes

Gamepad.axes返回的是gamepad控制元件的轴数据集,如手柄上的手摇杆Thumbstick、遥控器上的触控板Touchpad都是具有双轴向的元件。 当用户用手指推进摇杆或者轻触触控板时,都可以用一个二维笛卡尔坐标[x,y]来表示当前摇杆或触控板被触发的方位,如下图,返回一个-1.0~1.0的double数值组,一般将按水平、竖直的顺序排序,如axes[0]表示x轴位置、axes[1]表示y轴位置。

Gamepad Axes

update() {
  const axes = this.gamepad.axes; // 获取轴向数组
  const x = axes[0], y = axes[1], 
  dx = x - this._lastAxes[0], dy = y - this._lastAxes[1];
  // 控制画廊位移
  gallery.position.x += dx;
  gallery.position.y += dy;
  this._lastAxes = axes; 
}

上面通过计算两帧之间摇杆在x轴和y轴的位移,控制画廊的显示位置,当摇杆向左推时,画廊也向左移动。

GamepadPose

gamepad.pose属性返回的GamepadPose对象,与头显的VRPose对象类似,GamepadPose访问的是VR手柄的传感器(加速计和陀螺仪),可以直接获取gamepad的方向、位置、速度和加速度等信息。

属性 类型 说明
hasPosition bool gamepad是否具有position属性。
hasOrientation bool gamepad是否具有orientation属性。
position Float32Array 返回gamepad的位置矩阵
orientation Float32Array 返回gamepad的方向矩阵
angularAcceleration Float32Array 返回x, y, z轴每秒的角加速度
angularVelocity Float32Array 返回x, y, z轴每秒的角速度
linearAcceleration Float32Array 返回x, y, z轴每秒的线性加速度
linearVelocity Float32Array 返回x, y, z轴的线性速度

hasPosition与hasOrientation

只有3-DoF的gamepad如Gear VR和Daydream的Controller只包含orientation方向矩阵,因此hasOrientationtruehasPositionfalse; 而6-DoF的gamepad如Oculus touch和HTC Vive Controller由于orientationposition兼具,因此hasOrientationhasPosition都为true

position与orientation

GamepadPose最重要的属性,通过这两个属性可以将现实的手柄映射到VR三维世界中,比如当用户使用手柄玩射击游戏时,就需要获取每一帧gamepad的oritentation,并赋值给3d场景里的枪支模型。

update() {
    const { orientation, position } = this.gamepad.pose;
    controller.quaternion.fromArray( orientation ); // 将方向矩阵赋值给遥控器模型
    controller.position.fromArray( position ); // 将位置矩阵赋值给遥控器模型
}

Drop Dead 手枪射杀丧尸

Acceleration与Velocity

GamepadPose还提供了一系列运动属性:角加速度、角速度、线性速度、线性加速度,我们可以根据这些属性进行更丰富的物理行为,比如使用加速度×质量来计算物体受力情况,适用于诸如砍杀、击球等复杂运动形式,这里就不展开细说了。


小结

Gamepad API提供的丰富属性足以让我们实现各种类型手柄的交互,后续,我将对各主流VR类型进行针对性实现,根据交互的复杂性,将按照Cardboard→Gear VR→Daydream→Oculus Rift 由屌丝到高富帅的路线一一介绍。

0
推荐阅读