虚拟现实又重新成为热门话题!所有通常的嫌疑人都在参与:HTC、微软、三星和Facebook等都在兜售各自的设备。不过,这些可预测的参与者不应该独享乐趣!
您创建网站。您了解一些Javascript。您有一台移动设备。您也可以分一杯羹!WebVR 已经到来,而且学习起来并不难。如果您已经了解 three.js 的基础知识,您可能会惊讶于它启动起来是多么简单。如果您从未使用过 three.js,这将是一种学习它的有趣方式。
我制作网站已经有一段时间了,但直到最近几年我才开始探索前端技术在网站之外的更多用途。在花费一些时间使用画布和 three.js 等工具后,我的思维被打开,看到了网络的这一方面可以为我们开发人员(和艺术家!)提供的巨大潜力。
我走上了用 Javascript 制作迷幻视觉效果的道路,并且因此成为了视听科技组合 Polyop 的成员之一。作为黑胶唱片发布的一部分,我们创建了 一个使用 three.js 和 webVR 控件构建的 360 度音乐视频。我想与大家分享我在开发过程中学到的一些基本概念。
但我没有那些花哨的眼镜
不可否认,没有装备似乎是一个进入门槛。但是,本教程的大部分内容不需要任何额外的硬件,因此您仍然可以通过移动手机来探索您将创建的 3D 世界,从而获得乐趣。
要体验本教程的 VR 部分,您需要某种 VR 查看器。最便宜的方法是购买一个可以将您的手机变成 VR 耳机的耳机,您只需将手机插入其中即可。这些耳机的价格从 3 英镑到 50 英镑不等,因此可以四处看看,找到最适合您和您预算的耳机。“Google Cardboard”是您听到关于这些类型设备的术语。
我们将要制作什么
这是一个演示。 我们将要执行的步骤的所有源代码也可在 GitHub 上获得。
如果您在手机或平板电脑上查看,可以通过移动设备环顾四周。如果您使用的是笔记本电脑,则必须单击并拖动。如果您有手机的 VR 查看器,则可以通过单击“开始 VR”按钮进入实际的 VR 模式。
我们将分三个部分进行处理
创建场景
有一些 three.js 使用经验的用户可能希望跳过此部分,直接进入 VR 内容。
Three.js 已成为 Web 开发人员创建 3D 场景的首选库。不要让这个额外的维度吓到您;开始使用并不难!在我们开始考虑 VR 之前,我们将创建一个简单的 3D 世界,其中包含许多立方体,并缓慢旋转。
如果您不熟悉 three.js,我建议您查看文档中包含的 “创建场景”教程。它比我将要介绍的更详细一些,您很快就能启动并运行一个旋转的立方体。否则,请随时直接跳入这里,我们仍然会慢慢来。
设置
首先,我们需要设置一个包含 three.js 库的文档。您可以使用 Bower、npm 安装,或者保持简单,从 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 src="lib/three.js"></script>
<script>
// All scripts will go here
</script>
</body>
</html>
现在我们需要设置场景、相机和渲染器。场景充当所有对象的容器。相机是这些对象之一,它为我们提供了场景内的视角。渲染器获取来自相机的视图,并将其绘制到画布元素上。
// 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
document.body.appendChild( renderer.domElement );
我们还需要告诉渲染器渲染场景
// Render the scene
renderer.render( scene, camera );
从现在开始,您应该确保此渲染在代码中最后执行。稍后,我们将在 animate()
函数内部每帧触发它。
此时,您的场景应该会渲染一个页面上的画布元素,但您只会看到黑色。
让我们向场景中添加一个立方体
它包含几何体和材质,并通过网格组合在一起
// 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()
以启动动画循环。
我们的场景完成了!如果您遇到困难,请查看 此步骤的源代码,我已尽最大努力包含描述性注释。您会注意到我已稍微重新排列了本文中代码片段的顺序,并更好地使用了变量名称。
是时候变得虚拟了
在我们开始之前,了解我们实际在玩什么很有帮助!WebVR 网站对此进行了很好的总结
WebVR 是一种实验性的 JavaScript API,它允许访问虚拟现实设备,例如 Oculus Rift、HTC Vive、Samsung Gear VR 或 Google Cardboard,在您的浏览器中。
目前,该 API 仅在特殊的浏览器版本中有效,这可能很有趣,但缺乏受众。幸运的是,对于我们来说,WebVR Polyfill 及时出现拯救了局面。它使您的 VR 作品可以通过 Google Cardboard(或类似的查看器)在移动设备上可用,同时还允许用户在没有 VR 查看器的情况下查看相同的内容。您应该知道,polyfill 不支持任何其他 VR 设备,例如 Oculus Rift 或 HTC Vive。
要使用 polyfill,请在所有其他脚本之前在页面中包含 该脚本。如果您没有包含它,则本教程的接下来的两部分将无法工作。
控件
任何虚拟现实体验的关键组成部分都是捕捉用户的运动,并使用这些信息更新虚拟场景中相机的方向。我们可以在 three.js 中使用 VRControls
构造函数实现这一点。VRControls
不是库自带的,而是作为额外功能,您可以在 存储库中找到。您应该在 three.js 库之后,将其包含在单独的脚本标签中。
你会惊讶于它的实现是多么简单。首先,创建控件,并将相机传入。
var controls = new THREE.VRControls( camera );
现在这意味着控件将影响相机,而相机本质上只是场景中的一个对象,就像任何其他网格一样。如果你想的话,你可以使用这些控件来旋转一个立方体而不是相机。
在你的 `animate()` 函数中,你还需要告诉控件在每一帧都更新。
controls.update();
就是这样!如果你使用移动设备查看你制作的内容,你应该能够通过移动设备“环顾”场景。在没有这些功能的笔记本电脑上,你必须用鼠标点击并拖动,这种点击和拖动的回退是我们使用 WebVR polyfill 获得的额外奖励。

如果你卡住了,可以查看 此步骤的源代码。
VR 效果
此时,你可能已经对你的作品感到满意了。使用设备的运动环顾四周非常有趣,并且为制作一些很酷的东西开辟了各种可能性。在制作 Polyop 的交互式视频 时,我觉得这种行为已经足够沉浸式了,并且选择不引入立体声功能。
但是我承诺了真正的 VR,所以这就是你来的原因!难题的最后一块是让 three.js 渲染两张独立的图像,每只眼睛一张。我们将使用 VREffect 构造函数来实现这一点。就像你对 VRControls 所做的那样,包含脚本,然后我们就可以开始了。首先我们需要定义效果
effect = new THREE.VREffect(renderer);
effect.setSize(window.innerWidth, window.innerHeight);
我们定义一个新的 VREffect,并将渲染器传入。从现在开始,我们不需要处理渲染器,它将由 VREffect 处理。这就是为什么我们现在设置效果的大小而不是渲染器的大小。重要的是,我们需要替换我们在 animate 函数中渲染的方式
effect.render( scene, camera );
我们现在告诉效果进行渲染,而不是渲染器。目前没有任何变化。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`,我们将向它提供我们正在渲染到的画布元素。
document.querySelector('#startVR').addEventListener('click', function() {
vrDisplay.requestPresent([{source: renderer.domElement}]);
});
为了避免滥用 webVR API,要求你将任何 `requestPresent` 调用包装在事件监听器中。此事件监听器在具有 “startVR” ID 的按钮元素的点击时触发。

我们需要做的最后一件事是确保渲染器调整大小后所有内容都能正确渲染。这不仅发生在屏幕尺寸发生变化时,也发生在我们进出 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
window.addEventListener('vrdisplaypresentchange', onResize);
// Resize the renderer canvas if the browser window size changes
window.addEventListener('resize', onResize);
`onResize()` 函数重置效果(以及渲染器)的大小,同时还更新相机的一些属性。
再次,如果你感到有点困惑,可以查看 此最后一步的源代码。
总结
恭喜!你正式进入了网络空间。如何使用你的新能力?
为什么不基于我们今天已经完成的工作继续构建呢?也许尝试通过使用灯光和不同的几何体/材质将场景转换为更美观的东西?也许你甚至可以尝试使用音频 API 使对象随着音乐跳动?给你一个想法,这是我之前制作的一个。

非常简单明了,并且是 Web VR 的一个很好的入门教程。我很高兴在这个假期期间玩它。干得好。
哇!非常感谢你的时间!这是一个非常有解释性的教程,并且 -正如 @Paul 所说- 是进入 WebVR 世界的一个很好的入门教程 :)
新年快乐!