WebVR 1.0 API 提案介绍

2016 年将是虚拟现实的丰收之年。许多消费级 VR 产品将最终上市,许多顶级软件公司正在加紧支持这些新设备。这种新媒介也推动了浏览器供应商对 Web 支持的需求。WebVR 的发展主要集中在令人难以置信的 观看体验 以及用于创建在线 VR 内容的 工具 上。

VR_images
Mozilla VR 团队一直致力于在浏览器中支持在线 VR 内容的创建和显示。本周标志着 WebVR 的一个里程碑。Mozilla 团队与 Google Chrome 团队的 Brandon Jones 密切合作,很高兴地宣布 WebVR API 提案 1.0 版本发布。

最近的 VR 技术进步和社区反馈使我们能够改进 API 以满足开发人员的需求。

一些改进包括:

  • 特定于 VR 的设备渲染和显示处理。
  • 能够在 WebVR 页面之间进行链接。
  • 一个可以枚举 VR 输入的输入处理方案,包括 六自由度 (6DoF) 运动控制器。
  • 适应坐姿和站姿体验。
  • 适合桌面和移动设备使用。

我们很高兴与大家分享 API 的这些改进。请记住,上面的列表只是所做更改的一小部分。有关所有详细信息,请查看 完整的 API 草案,并查看 Brandon 的博客文章

本文重点介绍了所提议 API 的基本用法,这需要了解一些复杂的概念,例如 矩阵数学。作为替代方案,您可以通过查看 A-FrameWebVR 样板 来快速开始使用 WebVR,这两者都是基于 API 构建的。

在深入研究之前,我们想特别感谢 Chris Van Wiemeersch (Mozilla)、Kearwood “Kip” Gilbert (Mozilla)、Brandon Jones (Google) 以及 Justin Rogers (Microsoft) 为创建此规范做出的贡献。

实施路线图

我们计划在今年上半年将 1.0 API 的稳定版本发布到 Firefox Nightly 中。您可以在 Bugzilla 上跟踪所有详细信息,或在 iswebvrready.org 上查看平台支持状态更新。

想要今天就开始?目前,开发人员可以使用 Brandon Jones 的 实验性构建 Chromium 来实验新 API 的概念验证实现。

无论是 three.js 还是 WebVR Polyfill(如上所述,由 WebVR 样板 使用)都有开放的拉取请求以支持最新的 API。

VR 体验的组成部分

让我们看一下任何 VR 体验所需的关键组件

  1. 我们正在渲染内容的 VR 显示器。
  2. 用户的姿态。耳机在空间中的方向和位置。
  3. 定义立体分离和视野的视点参数。

以下是将内容传入耳机的流程序列

  1. navigator.getVRDisplays() 以检索 VR 显示器。
  2. 创建一个 <canvas> 元素,我们将使用它来渲染内容。
  3. 使用 VRDisplay.requestPresent() 传入画布元素。
  4. 创建 VR 设备特定的动画循环,我们将在其中执行内容渲染。
    1. VRDisplay.getPose() 以更新用户的姿态。
    2. 执行计算和渲染。
    3. 使用 VRDisplay.submitFrame() 以向合成器指示何时准备好将画布元素内容呈现在 VR 显示器中。

以下部分将详细描述每个操作。

使用 VR 显示器

显示 VR 内容的设备对 帧速率视野 和内容呈现有非常具体的显示要求,这些要求与标准桌面显示器分开处理。

枚举 VR 显示器

要检索浏览器可用的 VR 显示器,请使用 navigator.getVRDisplays() 方法,该方法返回一个承诺,该承诺将解析为一个 VRDisplay 对象数组

navigator.getVRDisplays().then(function (displays) {
  if (!displays.length) {
    // WebVR is supported, no VRDisplays are found.
    return;
  }

  // Handle VRDisplay objects. (Exposing as a global variable for use elsewhere.)
  vrDisplay = displays.length[0];
}).catch(function (err) {
  console.error('Could not get VRDisplays', err.stack);
});

请记住

  • 您必须将 VR 耳机插入并打开电源,然后才会枚举任何 VR 设备。
  • 如果您没有 VR 耳机,则可以通过打开 about:config 并将 dom.vr.cardboard.enabled 设置为 true 来模拟设备。
  • 使用 Firefox Nightly for AndroidFirefox for iOS 的用户将枚举一个 Cardboard VR 设备,用于与 Google Cardboard 配合使用。

创建渲染目标

要确定渲染目标大小(即画布大小),请创建一个足够大的渲染目标,以容纳左右眼视口。要查找每个眼睛的大小(以像素为单位)

// Use 'left' or 'right'.
var eyeParameter = vrDisplay.getEyeParameters('left');

var width = eyeParameter.renderWidth;
var height = eyeParameter.renderHeight;

将内容呈现在耳机中

要将内容呈现在耳机中,您需要使用 VRDisplay.requestPresent() 方法。此方法将 WebGL <canvas> 元素作为参数,该元素表示要显示的查看表面。

为了确保 API 不被滥用,浏览器需要一个用户发起的事件,以便首次用户进入 VR 模式。换句话说,用户必须选择启用 VR,因此我们将它包装到一个 click 事件处理程序中,该处理程序位于一个标记为“进入 VR”的按钮上。

// Select WebGL canvas element from document.
var webglCanvas = document.querySelector('#webglcanvas');
var enterVRBtn = document.querySelector('#entervr');

enterVRBtn.addEventListener('click', function () {
  // Request to present WebGL canvas into the VR display.
  vrDisplay.requestPresent({source: webglCanvas});
});

// To later discontinue presenting content into the headset.
vrDisplay.exitPresent();

设备特定的 requestAnimationFrame

现在我们已经设置了渲染目标,并拥有了在耳机中渲染和呈现正确视图所需的必要参数,我们可以为场景创建一个渲染循环。

我们希望以 VR 显示器的优化刷新率来执行此操作。我们使用 VRDisplay.requestAnimationFrame 回调

var id = vrDisplay.requestAnimationFrame(onAnimationFrame);

function onAnimationFrame () {  
  // Render loop.
  id = vrDisplay.requestAnimationFrame(onAnimationFrame);
}

// To cancel the animation loop.
vrDisplay.cancelRequestAnimationFrame(id);

此用法与您可能已熟悉的标准 window.requestAnimationFrame() 回调相同。我们使用此回调将位置和方向姿态更新应用于我们的内容,并渲染到 VR 显示器。

从 VR 显示器检索姿态信息

我们需要使用 VRDisplay.getPose() 方法检索耳机的方向和位置

var pose = vrDisplay.getPose();

// Returns a quaternion.
var orientation = pose.orientation;

// Returns a three-component vector of absolute position.
var position = pose.position;

请注意

  • 如果无法确定方向和位置,方向和位置将返回 null
  • 有关详细信息,请参阅 VRStageCapabilitiesVRPose

将场景投影到 VR 显示器

为了在耳机中正确地立体渲染场景,我们需要视点参数,例如偏移量(基于 瞳距或 IPD)和视野 (FOV)。

// Pass in either 'left' or 'right' eye as parameter.
var eyeParameters = vrDisplay.getEyeParameters('left');

// After translating world coordinates based on VRPose, transform again by negative of the eye offset.
var eyeOffset = eyeParameters.offset;

// Project with a projection matrix.
var eyeMatrix = makeProjectionMatrix(vrDisplay, eyeParameters);


// Apply eyeMatrix to your view.
// ...

/**
 * Generates projection matrix
 * @param {object} display - VRDisplay
 * @param {number} eye - VREyeParameters
 * @returns {Float32Array} 4×4 projection matrix
 */
function makeProjectionMatrix (display, eye) {
  var d2r = Math.PI / 180.0;
  var upTan = Math.tan(eye.fieldOfView.upDegrees * d2r);
  var downTan = Math.tan(eye.fieldOfView.leftDegrees * d2r);
  var rightTan = Math.tan(eye.fieldOfView.rightDegrees * d2r);
  var leftTan = Math.tan(eye.fieldOfView.leftDegrees * d2r);
  var xScale = 2.0 / (leftTan + rightTan);
  var yScale = 2.0 / (upTan + downTan);

  var out = new Float32Array(16);
  out[0] = xScale;
  out[1] = 0.0;
  out[2] = 0.0;
  out[3] = 0.0;
  
  out[4] = 0.0;
  out[5] = yScale;
  out[6] = 0.0;
  out[7] = 0.0;
  
  out[8] = -((leftTan - rightTan) * xScale * 0.5);
  out[9] = (upTan - downTan) * yScale * 0.5;
  out[10] = -(display.depthNear + display.depthFar) / (display.depthFar - display.depthNear);

  out[12] = 0.0;
  out[13] = 0.0;
  out[14] = -(2.0 * display.depthFar * display.depthNear) / (display.depthFar - display.depthNear);
  out[15] = 0.0;

  return out;
}

将帧提交到耳机

VR 经过优化,可以最大限度地减少用户移动与渲染到耳机中的内容之间的不连续性。这对舒适(不令人反感)的体验很重要。这是通过使用 VRDisplay.getPose()VRDisplay.submitFrame() 方法直接控制事件发生的机制来实现的

// Rendering and calculations not dependent on pose.
// ...

var pose = vrDisplay.getPose();

// Rendering and calculations dependent on pose. Apply your generated eye matrix here to views.
// Try to minimize operations done here.
// ...

vrDisplay.submitFrame(pose);

// Any operations done to the frame after submission do not increase VR latency. This is a useful place to render another view (such as mirroring).
// ...

一般规则是尽可能晚地调用 VRDisplay.getPose(),并尽可能早地调用 VRDisplay.submitFrame()

演示、反馈和资源

正在寻找入门方法?以下是一些 示例应用程序集合,这些应用程序使用了 WebVR 1.0 API。此外,还可以查看下面列出的资源。

并请继续分享您的反馈!

此 API 提案的开发直接响应了不断发展的 VR 技术,以及来自社区的反馈和讨论。我们已经取得了良好的开端,您持续的反馈可以帮助我们做得更好。

我们邀请您向 WebVR API 规范 GitHub 存储库 提交问题和拉取请求。

资源

关于 Casey Yee

我在 Mozilla 的 WebVR 团队工作,并致力于研究如何使用 Web 技术构建高性能虚拟现实体验。

Casey Yee 的更多文章...


5 条评论

  1. 动感小前端

    太棒了!感谢分享!

    2016 年 3 月 2 日 下午 5:30

  2. Finbar Maginn

    很乐意看到为 Cardboard VR 格式化的输出!

    2016 年 3 月 16 日 上午 7:28

    1. Casey Yee

      @Finbar,
      在 Firefox for Android 上使用 WebVR API 时,你会得到一个 Cardboard 格式的输出。对于其他浏览器,请参考 Boris Smus 的 webvr-boilerplate (https://github.com/borismus/webvr-boilerplate/),它使用 requestFullscreen() 对行为进行了填充。

      2016 年 3 月 22 日 下午 10:49

  3. Jonas

    看起来很有趣。我需要试一试。有两个问题

    你打算如何处理延迟和 GC 中断?
    几年前(2 年?)我尝试用 WebGL 构建一个快速的 FPS 游戏,但 Firefox 的输入到输出延迟非常糟糕,而 Google Chrome 的延迟更糟糕(记得是接近 100 毫秒)。在 Firefox 中,即使代码经过优化,也存在 GC 暂停的问题(我不确定为什么 GC 会被触发,因为堆没有增长,但它仍然会中断游戏)。WebVR 是否有独立的更短的渲染管道?你完全依赖于重投影吗?最后,这可以应用于非 VR 的 webgl 渲染吗?

    2016 年 3 月 16 日 上午 07:47

    1. Casey Yee

      @jonas,
      我们目前跟踪从获取方向和位置数据到实际像素绘制到头戴设备的时间大约是 7 毫秒。这在我们使用 Oculus DK2 和参考场景 mozvr.com Sechelt 示例的 75hz 下每帧 13 毫秒的时间范围内。

      此外,随着最近对姿势预测的实现,我们获得了更低的感知延迟和更平滑的跟踪 (http://mozvr.ghost.io/webvr-oculus-pose-prediction-and-hw-latency-testing/)。

      关于 GC 暂停问题,这绝对是一个需要解决的持续问题,但对于 VR,我们可以通过两种方式来帮助掩盖这些问题

      – 工作线程中的 WebGL,将使我们能够将渲染从主线程中移出,并避免任何卡顿和挂起 (https://blog.mozilla.org/research/2014/07/22/webgl-in-web-workers-today-and-faster-than-expected/)

      – 帧重投影/异步时间扭曲,我们生成中间帧来覆盖帧率低于所需 VR 刷新率的情况。 (https://developer.oculus.com/blog/asynchronous-timewarp-examined/)

      这一切绝对还在进行中,但结果令人鼓舞。在许多情况下,结果非常接近甚至与原生不可区分。

      2016 年 3 月 22 日 下午 12:53

本文的评论已关闭。