hxh

WebVR 响应式音频 | CSS-Tricks

hxh · 2017-01-16翻译 · 927阅读 原文链接

虚拟现实再次成为热点!所有的猜测来自包括:htc,microsoft,Samsung,和 Facebook 等等,它们都在兜售各自的设备。但是,不应该只有这些预料中的参与者享受这些乐趣!

如果你是开发网站的,那你对 Javascript 多少有所了解。如果你拥有一个移动设备,你也能拥有这份虚拟馅饼了!WebVR 就在这里,学习它并不困难。如果你了解过 three.js 的基础知识,你可能会惊讶于让它运行是多么简单。如果你没有使用过 three.js,接下来将会以一种有趣的方式让你学习它。

我曾经做了相当一段时间的网站开发,但只是在最后的几年里,我才探索开发网站之外的前端技术。花了一些时间去使用 canvas 和 three.js 这些工具,我对于网站能够提供给开发者(和艺术家!)的巨大潜力有了更多的认识。

Polyop-ceremony,是利用 three.js 和 WebVR controls 开发的音乐视频。

我用 Javascript 和基于 Polyop 三分之一的视听技术制造出迷幻的视觉效果。作为发行版的一部分,我们创建了一个 three.js 开发的 360 度音乐视频和 webVR controls。当我开始开发它的时候就想着和大家分享这些基础概念。

但我没有花俏的眼镜

不可否认,缺少装备似乎是进入 VR 世界的阻碍。但是,本教程的大部分内容都不需要任何额外的硬件,因此你任然可以移动你的手机去探索你创建的 3D 世界。

为了体验本教程的 VR 部分,你需要一些 VR 浏览设备。最便宜的方法是买一个头显,然后把你的手机变成 VR 头显,你只需要将手机插入头显就可以到处移动。头显的价格在 £3 到 £50 之间,因此选择一个适合你和你预算的设备吧。“Google Cardboard” 就是这类设备。

我们将要做什么

这里有一个 demo. 所有的源码都可以在 GitHub 上获取。

如果你正在使用手机或者平板电脑阅读,你可以将移动设备观看效果。如果你是用笔记本电脑阅读,你需要点击并拖动观看效果。如果你的手机有 VR 浏览器,点击“开启 VR”按钮可以进入 VR 模式。

本教程会分为三部分:

  1. 创建 three.js 场景 (+ demo)

  2. 添加 VR Controls (device motion) (+ demo)

  3. 应用 the VR Effect (stereoscopic picture) (+ demo)

创建场景

如果你有 three.js 的开发经验,可以跳过这节,直接阅读 VR stuff。

Three.js 已成为 web 开发者们喜爱的用于创建 3D 场景的库。不要让多出的一个维度吓到你;使用它并不困难!在考虑 VR 之前,我们先创建一个简单的 3D 世界,里面有一堆缓慢旋转的立方体。

如果你是没接触过 three.js,我建议先阅读它的文档中的 “creating a scene” 。里面介绍了更多的细节,你很快就能够创建一个旋转的立方体了。或者直接阅读接下面的内容,慢慢的开始。

开始

首先我们需要创建一个引用了 three.js 的文档。你可以使用 Bower,npm 下载 three.js,或者直接引用 a CDN 中的文件。

请注意,three.js 的 API 还在不断的改变。本教程使用的是r82,任何库使用最新版本总是好的,但为了易于理解,建议使用与例子相同的版本。

<!DOCTYPE html>
<html lang="en">

  <head>
    <title>WebVR Tutorial</title>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>

  <body>
    </script>
    <script>
      // All scripts will go here
    </script>
  </body>

</html>

现在我们需要创建场景,相机和渲染器。场景是包含所有对象的一个容器。相机就是其中的一个对象,为我们提供场景内部的视野。渲染器获取相机中的视野,并将其渲染到 canvas 元素上。

// Create the scene and camera
// 创建场景和相机
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );

// Create the renderer
// 创建渲染器
var renderer = new THREE.WebGLRenderer();

// Set the size of the renderer to take up the entire window
// 将渲染器的尺寸设置为窗口大小
renderer.setSize( window.innerWidth, window.innerHeight );

// Append the renderer canvas element to the body
// 将渲染器的 canvas 元素添加到 body 中
document.body.appendChild( renderer.domElement );

我们需要告诉渲染器去渲染场景:

// Render the scene
// 渲染场景
renderer.render( scene, camera );

现在,你应该确保你的代码最后发生了渲染。我们会将这段代码放在在animate()里面,每一帧都调用它。

此时,页面中的 canvas 元素应该将你的场景渲染出来了,但是你能看到的只有黑色。

向场景添加一个立方体

一个立方体由几何体(geometry)和材质(material)组成,包含在一个网格(mesh)中。

// Create cube
// 创建立方体
var material = new THREE.MeshNormalMaterial();
var geometry = new THREE.BoxGeometry( 50, 50, 50 );
var mesh = new THREE.Mesh( geometry, material );

// Add cube to scene
// 将立方体添加到场景中
scene.add(mesh);

现在你应该看到一个立方体被渲染出来了,耶~

让我们用for来循环创建大量的立方体:

var cubes = [];

for (var i = 0; i < 100; i++) {

  var material = new THREE.MeshNormalMaterial();
  var geometry = new THREE.BoxGeometry( 50, 50, 50 );
  var mesh = new THREE.Mesh( geometry, material );

  // Give each cube a random position
  // 给每一个立方体随机的位置
  mesh.position.x = (Math.random() * 1000) - 500;
  mesh.position.y = (Math.random() * 1000) - 500;
  mesh.position.z = (Math.random() * 1000) - 500;

  scene.add(mesh);

  // Store each mesh in array
  // 将每一个网格保存到数组中
  cubes.push(mesh);

 }

你可能注意到了,我改变每一个立方体的 position 属性,给它们一个随机的位置。X,Y 和 Z 分别代表立方体在每一个轴上的位置。相机的位置为(0,0,0),在场景的中心。为每一个立方体所有轴提供一个随机的值(-500 到 500 之间),这些立方体就位于相机的四面八方。

我将立方体都保存在一个数组中,接着我们就能够为它们提供动画。我们需要创建一个animate()方法,每一帧都执行:

function animate() {

  requestAnimationFrame( animate );

  // Every frame, rotate the cubes a little bit
  // 每一帧都稍微旋转立方体
  for (var i = 0; i < cubes.length; i++) {
    cubes[i].rotation.x += 0.01;
    cubes[i].rotation.y += 0.02;
  }

  // Render the scene
  // 渲染场景
  renderer.render( scene, camera );

}

animate()方法遍历数组 cubes ,更新每一个网格的 rotation 属性。它每一帧都会遍历,因为我们递归调用了 requestAnimationFrame。你也会注意到我将renderer.render()移到了这个方法中,因此每一帧都会渲染一遍场景。

要确保你的脚本中调用了animate()开启动画循环。

我们的场景完成了!如果你还在挣扎,可以看一下这一步的源码,我尽我所能的加上了描述性的注释了。你会发现我稍微重构了这段代码,以及更好的使用变量名。

Time to get virtual

开始之前,最好知道我们在玩什么!这个 WebVR 网站总结的很好:

> WebVR是在浏览器中为虚拟现实设备,例如 Oculus RiftHTC ViveSamsung Gear VR,或者Google Cardboard,提供访问入口的实验性 JavaScript API。

目前这个 API 只能在特定的浏览器上使用,它可能会很有趣,却缺少了观众。辛运的是, WebVR Polyfill 出现了。它让你的 VR 作品能够通过 Google Cardboard (或者类似的浏览设备)在移动设备上展示,同时用户也能在没有 VR 浏览设备的情况下观看。你应该知道的是,polyfill 并不支持任何如 Oculus Rift 或者 HTC Vive 的 VR 设备。

使用 polyfill 需要在页面所有的脚本前面中引入该脚本。如果不引入的话,本教程接下来的两部分内容都不会有效果。

控制器

虚拟现实实验中一个重要的组成部分是捕获用户的手势,根据手势更新相机在虚拟场景中的方向。我们可以用 three.js 的 VRControls 实现控制器。VRControls 不需要其它库来创建,你可以在 代码库中找到它。它应该在 three.js 之后引入。

你会惊讶于它实现起来是如此的简单。首先,创建控制器,传递给相机:

`var controls = new THREE.VRControls( camera );`

现在 controls 能够控制相机,它本质上和其它网格一样,是场景中的一个对象。你可以使用 controls 来使立方体旋转。

anmimate()中告诉 controls 更新。

`controls.update();`

就是它,如果你在移动设备上查看效果,你应该能够通过移动设备“环顾”场景。在笔记本电脑上没有这种能力,你需要点击并拖动鼠标查看,这是 WebVR polyfill 实现的兼容效果。

如果你遇到困难,看一下这部分的源码

VR 效果

现在你可能已经满意创建出来的东西了。对着你的设备使用手势环顾四周很有趣吧,这为创作一些很酷的东西开辟了各种可能性。制作 Polyop 的互动视频时,我感觉这样身临其境的效果已经足够了,因此没有引入立体特征。

但是,我承诺过是真正的 VR,接下来就是听觉!最后一步就是让 three.js 渲染出两张图片,分别对应一只眼睛。我们需要使用 VREffect 构造方法。就像使用 VRControls 一样,引入脚本。首先需要定义 effect:

effect = new THREE.VREffect(renderer);
effect.setSize(window.innerWidth, window.innerHeight);

我们定义了一个新的 VREffect,传递给渲染器。现在开始我们不再需要处理渲染器了,它由 VREffect 去处理。这就是为什么设置的是 effect 而不是渲染器的尺寸。重要的是,我们需要改变 animate 方法中的渲染方式:

`effect.render( scene, camera );`

现在是告诉 effect 而不是渲染器去渲染,其它的都不变。只需要向告诉渲染器那样告诉 VREffect 去渲染。为了实现想要的立体效果,我们需要多做一点。

首先,我们需要搜索 VR 连接设备。因为我们使用了 WebVR Polyfill,我们得到了一个连接“设备”,Google Cardboard。是这样获取它的:

var vrDisplay;
navigator.getVRDisplays().then(function(displays) {
    if (displays.length > 0) {
     vrDisplay = displays[0];
   }  
});

navigator.getVRDisplays 返回一个 promise 方法,在查找设备完成之后调用。我们只需要数组 displays 中的唯一一个设备,将它保存在全局变量vrDisplay中,这样我们就可以在任何地方使用它了。如果不适用 polyfill,数组中可能会有多个设备,你可能需要添加一些功能让用户去选择连接哪个设备了。幸运的是,现在我们不需要容纳小强尼和它的 50 个不同的 VR 设备了。

现在我们有自己的一个设备,定义为 vrDisplay,我们需要让它燃烧起来!使用 requestPresent,将用于渲染的 canvas 元素传递给它。

document.querySelector('#startVR').addEventListener('click', function() {
  vrDisplay.requestPresent([{source: renderer.domElement}]);
});

为了避免滥用webVR API,需要将它包含在一个事件监听器的回调函数中。现在可以点击 ID 为 “startVR” 的按钮启动它了。

我们需要做得最后一件事是确保在调整渲染器尺寸之后所有东西都能够被正确渲染。除了调整屏幕尺寸,切换 VR 模式也会改变渲染器的尺寸。

// Resize the renderer canvas
// 调整渲染器
function onResize() {
effect.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
}

// Resize the renderer canvas when going in or out of VR mode
// 进入 VR 模式之后调整渲染器
window.addEventListener('vrdisplaypresentchange', onResize);

// Resize the renderer canvas if the browser window size changes
// 如果浏览器窗口尺寸变化,调整渲染器。
window.addEventListener('resize', onResize);

onResize() 方法重置了 effect(也就是渲染器)的尺寸,同时也会更新相机的一些属性。

再次的,如果你感觉有点混乱,看这一步的源码

Once again, if you’re feeling a bit muddled, take a look at the source code of this final step.

总结

恭喜!你已经正式进入网络空间,有了新能力要做什么呢?

为什么不继续今天已经做的工作呢?也许可以尝试使用灯光和不同的几何体/材质变化场景,是它更加美观?也许你甚至已经使用了音频 API?给你个注意,这是我之前做的

相关文章