WebVR第5部分:设计和实现

本文概述

我喜欢让项目”完成”。我们已经走到了旅程的尽头-WebVR中天体重力模拟的诞生。

在最后一篇文章中, 我们将把高性能的仿真代码(第1, 2, 3条)插入到基于canvas可视化工具(第4条)的WebVR可视化工具中。

  1. ” n体问题”简介和体系结构
  2. 网络工作者为我们提供了其他浏览器线程
  3. WebAssembly和AssemblyScript用于我们的O(n²)性能瓶颈代码
  4. 画布数据可视化
  5. WebVR数据可视化

这是一篇较长的文章, 因此我们将跳过之前介绍的一些技术细节。如果你想了解方向, 请查阅以前的文章, 或危险阅读。

我们一直在探索浏览器的范式从单线程JavaScript运行时到多线程(网络工作者)高性能运行时(WebAssembly)的转变。这些性能桌面计算功能可在Progressive Web Apps和SaaS分发模型中使用。

WebVR演示

WebVR演示, 示例代码

VR将创建引人注目的, 无干扰的销售和营销环境, 以进行交流, 说服和衡量参与度(眼动和互动)。数据仍将是零和一, 但是预期的执行摘要和消费者体验将是WebVR-就像我们今天为平面Web构建移动仪表板体验一样。

这些技术还支持分布式浏览器边缘计算。例如, 我们可以创建一个基于Web的应用程序, 以在模拟中为数百万颗恒星运行WebAssembly计算。另一个示例是一个动画应用程序, 它可以在你编辑自己的作品时呈现其他用户的作品。

娱乐内容正在引领虚拟现实技术的发展, 就像移动设备上的娱乐一样。但是, 一旦VR正常(就像今天的移动设备优先设计), 那将是预期的体验(VR优先设计)。对于设计师和开发人员而言, 这是一个非常激动人心的时刻-VR是一种完全不同的设计范例。

如果你抓不住, 你不是VR设计师。这是一个大胆的声明, 今天是对VR设计的深入了解。你在阅读本文时就发明了这个领域。我的目的是分享我在软件和电影方面的经验, 以启动” VR优先设计”对话。我们彼此学习。

考虑到这些宏伟的预测, 我想以专业技术演示的形式完成此项目-WebVR是一个不错的选择!

WebVR和Google A-Frame

WebVR git repo是canvas版本的一个分支, 有几个原因。它使在Github页面上托管项目变得更加容易, 并且WebVR需要进行一些更改, 这些更改会使画布版本和这些文章变得混乱。

如果你还记得我们关于体系结构的第一篇文章, 我们将整个模拟委托给了nBodySimulator。

`nBodySimulator`

网络工作者的帖子显示, nBodySimulator具有一个step()函数, 每33ms模拟一次。 step()调用calculateForces()来运行我们的O(n²)WebAssembly模拟代码(第3条), 然后更新位置并重新绘制。在上一篇创建画布可视化的文章中, 我们从这个基类开始使用canvas元素实现了这一点:

/**

 * Base class that console.log()s the simulation state.

 */

export class nBodyVisualizer {

  constructor(htmlElement) {

    this.htmlElement = htmlElement

    this.resize()

    this.scaleSize = 25 // divided into bodies drawSize. drawSize is log10(mass)

    // This could be refactored to the child class. 

    // Art is never finished. It must be abandoned.

  }

  resize() {}

  paint(bodies) {

    console.log(JSON.stringify(bodies, null, 2))

  }

}

定义整合挑战

我们有模拟。现在, 我们希望与WebVR集成-无需重新设计项目。我们对仿真所做的任何调整都会在paint(bodies)函数的主UI线程中每33ms发生一次。

这就是我们衡量”完成”的方式。我很兴奋-让我们开始工作吧!

如何实现虚拟现实

首先, 我们需要一个设计:

  • VR由什么制成?
  • WebVR设计如何表达?
  • 我们如何与之互动?

虚拟现实可以追溯到时间的曙光。每个篝火故事都是微小的虚拟世界, 这些琐碎的细节掩盖了荒诞的夸张。

通过添加3D立体视觉效果和音频, 我们可以将篝火故事放大10倍。我的电影制作预算讲师曾经说过:”我们只为海报付费。我们不是在建立现实。”

如果你熟悉浏览器DOM, 就会知道它会创建树状的分层结构。

平面场景图

平面”场景图”。

网页设计中隐含的是查看者从”正面”进行查看。从侧面看, 将DOM元素显示为线, 从背面看, 我们仅看到<body>标签, 因为它遮盖了其子元素。

VR身临其境的体验的一部分是让用户控制他们的观点, 样式, 节奏和交互顺序。他们不必特别注意任何事情。如果你以编程方式移动或旋转相机, 则它们实际上会从VR疾病中呕吐出来。

请注意, VR疾病不是开玩笑。我们的眼睛和内耳都可以检测到运动。对于直立行走的动物来说非常重要。当那些运动传感器不同意时, 我们的大脑自然会认为我们的嘴巴又在胡说八道并呕吐。我们都是孩子一次。关于VR的这种生存本能已有许多文献报道。 Steam上免费提供” Epic Fun”头衔, 过山车是我发现的最好的VR病演示。

虚拟现实表示为”场景图”。场景图具有与DOM相同的树状图案, 以隐藏令人信服的3D环境的细节和复杂性。但是, 我们将查看器放置在他们想要向其拉动体验的位置, 而不是滚动和路由。

这是Google A-Frame WebVR Framework的Hello World场景图:

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <title>Hello, WebVR! • A-Frame</title>

    <meta name="description" content="Hello, WebVR! • A-Frame">

    <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>

  </head>

  <body>

    <a-scene background="color: #FAFAFA">

      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>

      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>

      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>

      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>

    </a-scene>

  </body>

</html>

该HTML文档在浏览器中创建一个DOM。 <a-*>标签是A-Frame框架的一部分, 而<a-scene>是场景图的根。在这里, 我们看到了场景中显示的四个3D图元。

平面网络浏览器中的A帧场景

平面网络浏览器中的A帧场景。

首先, 请注意, 我们正在通过平面网络浏览器查看场景。右下角的小面具邀请用户切换到3D立体模式。

虚拟现实中的A帧场景

虚拟现实中的A帧场景-每只眼睛一张图像。

从理论上讲, 你应该能够:

  1. 在手机上打开
  2. 抬起手机面对面
  3. 在新现实的辉煌中欢欣鼓舞!

如果没有VR耳机的精美镜头, 我永远无法做到这一点。你可以在便宜的价格(基于Google Cardboard的基本设备)上为Android手机获得VR耳机, 但是, 对于开发内容, 我建议使用独立的HMD(头盔显示器), 例如Oculus Quest。

就像潜水或跳伞一样, 虚拟现实是一项齿轮运动。

虚拟现实设计师学习”悬崖”

重力和光的现实

欢迎来到重力与光线的舒适现实。

请注意, A帧Hello World场景具有默认的照明和摄像头:

  • 立方体的面是不同的颜色-立方体是自阴影的。
  • 立方体在平面上投下阴影-有定向光。
  • 立方体和平面之间没有缝隙-这是一个有重力的世界。

这些关键提示会向观看者说:”放轻松, 这东西在你的脸上是完全正常的。”

另请注意, 默认设置在上面的Hello World场景代码中是隐式的。 A-Frame明智地提供了明智的默认设置, 但请注意-平面设计人员必须交叉的摄像头和照明设备才能创建VR。

我们将默认照明设置视为理所当然。例如, 按钮:

纽扣

注意这种隐式照明在设计和摄影中的普及程度。甚至”平面设计”按钮也无法摆脱网络的默认照明-它向右下方投射了阴影。

设计, 交流和实现照明和摄像头设置是WebVR设计师的学习重点。 “电影的语言”是文化规范的集合, 表现为不同的相机和照明设置, 可以将故事情感地传达给观众。负责在场景周围设计/移动灯光和相机的电影专业人士是握把部门。

回到我们的虚拟现实

现在, 让我们重新开始工作。我们的天体WebVR场景具有类似的模式:

<!DOCTYPE>

<html>

<head>

  <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>

  <script src="https://unpkg.com/[email protected]/dist/aframe-event-set-component.min.js"></script>

  <script src="main.js"></script>

</head>

<body>

  <a-scene id="a-pocket-universe">

    <a-sky color="#222"></a-sky>

    <a-entity geometry="primitive: circle; radius: 12" position="0 0 -.5"

      material="color: #333; transparent: true; opacity: 0.5">

      <a-sphere color="black" radius=."02"></a-sphere>

    </a-entity>

    <a-entity id="a-bodies"></a-entity>

    <a-entity geometry="primitive: plane; width: 2; height: auto" position="0 -10 .3" rotation="55 0 0"

      material="color: blue"

      text="value: Welcome Astronaut!...">

    </a-entity>

    <a-entity id="rig" position="0 -12 .7" rotation="55 0 0">

      <a-camera>

        <a-cursor color="#4CC3D9" fuse="true" timeout="1"></a-cursor>

      </a-camera>

    </a-entity>

  </a-scene>

</body>

</html>

该HTML文档加载了A-Frame框架和一个交互插件。我们的场景始于<a-scene id =” a-pocket-universe”>。

在内部, 我们从<a-sky color =”#222″> </ a-sky>元素开始, 为场景中未定义的所有内容设置背景颜色。

接下来, 我们为观众创建一个”轨道平面”, 以便观众在我们陌生而未知的世界中飞翔。我们将其创建为圆盘和(0, 0, 0)处的黑色小球。没有这个, 转弯对我来说是”没有根据的”:

    <a-entity geometry="primitive: circle; radius: 12" position="0 0 -.5"

      material="color: #333; transparent: true; opacity: 0.5">

      <a-sphere color="black" radius=."02"></a-sphere>

    </a-entity>

接下来, 我们定义一个集合, 可以在其中添加/删除/重新定位A-Frame实体。

<a-entity id="a-bodies"></a-entity>

这是nBodyVisualizers绘画(主体)执行其工作的许可。

然后, 我们在观众和这个世界之间建立关系。作为技术演示, 这个世界的目的是让观看者探索WebVR和支持它的浏览器技术。一个简单的”宇航员”叙述创造了一种玩耍的感觉, 而这个恒星的路标则是导航的另一个参考点。

   <a-entity geometry="primitive: plane; width: 2; height: auto" position="0 -10 .3" rotation="55 0 0"

      material="color: blue"

      text="value: Welcome Astronaut!\n  ...">

    </a-entity>

这样就完成了场景图。最后, 我希望用户和这个棘手的世界之间在电话演示中进行某种交互。我们如何在VR中重新创建” Throw Debris”按钮?

该按钮是所有现代设计的主要元素-VR按钮在哪里?

WebVR中的交互

虚拟现实有它自己的”之上”和”下端”。观看者的首次互动是通过其化身或照相机进行的。这是所有要缩放的控件。

如果你是在台式机上阅读此书, 则可以使用WASD进行移动, 并使用鼠标旋转相机。此探索揭示了信息, 但没有表达你的意愿。

现实具有几个非常重要的功能, 这些功能在网络上并不常见:

  • 透视-当物体远离我们时, 物体会明显变小。
  • 遮挡-根据位置隐藏和显示对象。

VR模拟这些功能来创建3D效果。它们还可以在VR中用于显示信息和界面-并在演示交互之前设置心情。我发现大多数人只需要花一点时间就可以享受体验, 然后继续前进。

在WebVR中, 我们在3D空间中进行交互。为此, 我们有两个基本工具:

  • 碰撞-两个对象共享同一空间时触发的被动3D事件。
  • 投影-激活的2D函数调用, 列出与一条线相交的所有对象。

碰撞是最”类似于VR”的互动

在VR中, “碰撞”的确切含义是:当两个对象共享同一空间时, A-Frame会创建一个事件。

为了使用户”按下”按钮, 我们必须给他们一个棋子和一些东西来按下按钮。

不幸的是, WebVR尚不能假定控制器-许多人会在台式机或电话上查看平板版本, 许多人会使用Google Cardboard或Samsung的Gear VR之类的耳机来显示立体版本。

如果用户没有控制器, 则他们无法伸出手并”触摸”事物, 因此任何碰撞都必须与他们的”个人空间”有关。

我们可以给玩家一个宇航员形状的棋子来回走动, 但是强迫用户进入旋转的行星状of骨似乎有点令人反感, 这与我们设计的宽敞性背道而驰。

投影是在3D空间中的2D”类似于Web”的单击

除了”碰撞”, 我们还可以使用”投影”。我们可以在场景中投射一条线, 然后看一下它的触感。最常见的示例是”传送射线”。

传送射线描绘了世界上的一条线, 以显示玩家可以移动的位置。这个”投影”寻找着陆的地方。它在投影的路径中返回一个或多个对象。这是一个传送射线示例:

虚幻引擎默认内容中的传送射线

虚幻引擎默认内容中的传送射线。

请注意, 射线实际上是作为指向下方的抛物线实现的。这意味着它自然地与”地面”相交, 就像抛出的物体一样。这自然也设置了最大的隐形传送距离。限制是VR中最重要的设计选择。幸运的是, 现实有许多自然的局限性。

投影将3D世界”拉平”为2D, 因此你可以指向东西以像鼠标一样单击它。第一人称射击游戏是在精致而令人沮丧的按钮上进行的”二维点击”精心制作的游戏-常常带有精心制作的故事, 以解释为什么那些没用的按钮在”点击”你的背上就不好了。

VR中的枪支之所以多, 是因为枪支已被完善为精确而可靠的3D鼠标-而点击就是消费者知道的, 而无需学习。

投影还可以确保与场景之间的距离安全。记住, 接近VR中的某些事物自然会遮盖所有其他尚未显露其重要性的事物。

不使用”注视”的控制器进行投影

要在不带控制器的WebVR中创建此交互原语, 我们可以将观众的”凝视”投射为视线”光标”。可以以编程方式使用此光标与具有”保险丝”的对象进行交互。这会以蓝色小圆圈的形式传达给查看者。现在我们点击!

如果你还记得篝火的故事, 那么谎言越大, 出售它所需要的细节就越少。一个明显而荒谬的”凝视”互动是凝视太阳。我们使用此”凝视”来触发向模拟添加新的”碎片”行星。从来没有观众质疑过这种选择-VR荒谬时非常吸引人。

在A-Frame中, 我们表示摄像机(玩家的隐形兵), 并且将视线”光标”表示为我们的摄像机装备。将<a-cursor>放置在<a-camera>中会导致将相机的变换也应用于光标。当玩家移动/旋转其棋子(a摄像机)时, 它也会移动/旋转其凝视(a光标)。

// src/index.html

    <a-entity id="rig" position="0 -12 .7" rotation="55 0 0">

      <a-camera>

        <a-cursor color="#4CC3D9" fuse="true" timeout="1"></a-cursor>

      </a-camera>

    </a-entity>

在发出事件之前, 光标的”保险丝”要等到经过一秒钟的”凝视”。

我使用了默认照明, 因此你可能会注意到太阳的”后背”没有照明。虽然我没有走出轨道平面, 但我不认为这是太阳的工作方式。但是, 它适用于我们的现实技术演示海报。

另一种选择是将灯光放置在相机元素内部, 以便随用户移动。这将创造出更亲密的, 甚至可能更怪异的小行星矿工体验。这些是有趣的设计选择。

我们有一个整合计划

这样, 我们现在有了A框架<a-scene>与JavaScript仿真之间的集成点:

A帧<a-场景>:

  • 实体的命名集合:<a-entity id =” a-bodies”> </ a-entity>

  • 将发出投影事件的光标:<a-cursor color =”#4CC3D9″ fuse =” true” timeout =” 1″> </ a-cursor>

我们的JavaScript模拟:

  • nBodyVisWebVR.paint(bodies)-从模拟主体中添加/删除/重新放置VR实体

  • addBodyArgs(name, color, x, y, z, mass, vX, vY, vZ)为模拟添加新的碎片体

index.html加载main.js, 它初始化我们的模拟就像画布版本一样:

// src/main.js

import { nBodyVisualizer, nBodyVisWebVR } from ."/nBodyVisualizer"

import { Body, nBodySimulator } from ."/nBodySimulator"

window.onload = function() {

  // Create a Simulation

  const sim = new nBodySimulator()

  

  // this Visualizer manages the UI

  sim.addVisualization(new nBodyVisWebVR(document.getElementById("a-bodies"), sim)) 

  

  // making up stable universes is hard

  //                   name            color     x    y    z    m      vz    vy   vz

  sim.addBody(new Body("star", "yellow", 0, 0, 1, 1e9)) 

  sim.addBody(new Body("hot-jupiter", "red", -1, -1, 1, 1e4, .24, -0.05, 0))

  sim.addBody(new Body("cold-jupiter", "purple", 4, 4, .5, 1e4, -.07, 0.04, 0))

  // Start simulation  

  sim.start()

  

  // Add another

  sim.addBody(new Body("saturn", "blue", -8, -8, .1, 1e3, .07, -.035, 0))

}

你会在这里注意到, 我们将可视化工具的htmlElement设置为a-body集合以容纳主体。

通过JavaScript以编程方式管理A-Frame对象

在index.html中声明了场景之后, 我们现在就可以对可视化工具进行编码了。

首先, 我们设置nBodyVisualizer以从nBodySimulation主体列表中进行读取, 并在<a-entity id =” a-bodies”> </ a-entity>集合中创建/更新/删除A-Frame对象。

// src/nBodyVisualizer.js

/**

 * This is the WebVR visualizer. 

 * It's responsible for painting and setting up the entire scene.

 */

export class nBodyVisWebVR extends nBodyVisualizer {

  constructor(htmlElement, sim) {

    // HTML Element is a-collection#a-bodies.

    super(htmlElement)

    // We add these to the global namespace because 

    // this isn't the core problem we are trying to solve.

    window.sim = sim

    this.nextId = 0

  }

  resize() {}

在构造函数中, 我们保存A-Frame集合, 为凝视事件设置全局变量以查找模拟, 并初始化一个ID计数器, 以用于在模拟和A-Frame的场景之间进行匹配。

  paint(bodies) {

    let i

    // Create lookup table: lookup[body.aframeId] = body

    const lookup = bodies.reduce( (total, body) => {

      // If new body, give it an aframeId

      if (!body.aframeId) body.aframeId = `a-sim-body-${body.name}-${this.nextId++}`

      total[body.aframeId] = body

      return total

    }, {})

    // Loop through existing a-sim-bodies and remove any that are not in

    // the lookup - this is our dropped debris

    const aSimBodies = document.querySelectorAll(."a-sim-body")

    for (i = 0; i < aSimBodies.length; i++) {

      if (!lookup[aSimBodies[i].id]) {  

        // if we don't find the scene's a-body in the lookup table of Body()s, // remove the a-body from the scene

        aSimBodies[i].parentNode.removeChild(aSimBodies[i]); 

      } 

    }

    // loop through sim bodies and upsert

    let aBody

    bodies.forEach( body => {

      // Find the html element for this aframeId

      aBody = document.getElementById(body.aframeId)

      // If html element not found, make one.

      if (!aBody) {

        this.htmlElement.innerHTML += `

<a-sphere 

  id="${body.aframeId}"

  class="a-sim-body"

  dynamic-body 

  ${ (body.name === "star") ? "debris-listener event-set__enter='_event: mouseenter; color: green' event-set__leave='_event: mouseleave; color: yellow'" : ""} 

  position="0 0 0" 

  radius="${body.drawSize/this.scaleSize}" 

  color="${body.color}">

</a-sphere>`

        aBody = document.getElementById(body.aframeId)

      }

      // reposition

      aBody.object3D.position.set(body.x, body.y, body.z)

    })

}

首先, 我们遍历模拟主体以标记和/或创建查找表, 以将A-Frame实体与模拟主体匹配。

接下来, 我们遍历现有的A型车架车身, 并删除通过模拟修剪的任何车架, 以超出范围。这增加了体验的感知性能。

最后, 我们遍历sim主体以为缺失的主体创建一个新的<a-sphere>, 并使用aBody.object3D.position.set(body.x, body.y, body.z)重新定位其他主体

我们可以使用标准DOM函数以编程方式更改A帧场景中的元素。要向场景添加元素, 我们在容器的innerHTML后面附加一个字符串。这段代码对我来说很奇怪, 但是可以用, 但我发现没有什么比这更好的了。

你会注意到, 当我们创建要附加的字符串时, ” star”附近有一个三元运算符来设置属性。

<a-sphere 

  id="${body.aframeId}"

  class="a-sim-body"

  dynamic-body 

  ${ (body.name === "star") ? "debris-listener event-set__enter='_event: mouseenter; color: green' event-set__leave='_event: mouseleave; color: yellow'" : ""} 

  position="0 0 0" 

  radius="${body.drawSize/this.scaleSize}" 

  color="${body.color}">

</a-sphere>`

如果身体是”星星”, 我们添加一些描述其事件的额外属性。这是安装在DOM中后我们的星星的外观:

<a-sphere id="a-sim-body-star-0" 

class="a-sim-body" 

dynamic-body="" 

debris-listener=""

event-set__enter="_event: mouseenter; color: green"

event-set__leave="_event: mouseleave; color: yellow"

position="0 0 0" 

radius="0.36" 

color="yellow" 

material="" 

geometry=""></a-sphere>

碎片侦听器, 事件设置__输入和事件设置__三个属性设置了我们的交互, 并且是我们集成的最后一圈。

定义A帧事件和交互

我们在实体的属性中使用NPM包” aframe-event-set-component”, 以在观看者”注视”太阳时更改太阳的颜色。

这种”凝视”是观看者位置和旋转的投影, 并且互动会提供必要的反馈, 告知他们的凝视正在做某事。

现在, 我们的星际球有两个由插件启用的速记事件, event-set__enter和event-set__leave:

<a-sphere id="a-sim-body-star-0" 

...

event-set__enter="_event: mouseenter; color: green"

event-set__leave="_event: mouseleave; color: yellow"

…
></a-sphere>

接下来, 我们用碎片侦听器装饰星状球, 并将其实现为自定义A帧组件。

<a-sphere id="a-sim-body-star-0" 

...

debris-listener=""

…
></a-sphere>

A-Frame组件是在全局级别定义的:

// src/nBodyVisualizer.js

// Component to add new bodies when the user stares at the sun. See HTML

AFRAME.registerComponent('debris-listener', {

  init: function () {

    // Helper function

    function rando(scale) { return (Math.random()-.5) * scale }

    // Add 10 new bodies

    this.el.addEventListener('click', function (evt) {

      for (let x=0; x<10; x++) {

        // name, color, x, y, z, mass, vx, vy, vz

        window.sim.addBodyArgs("debris", "white", rando(10), rando(10), rando(10), 1, rando(.1), rando(.1), rando(.1))

      }

    })

  }

})

该A帧组件的作用类似于”点击”侦听器, 可以由凝视光标触发, 以将10个新的随机物体添加到场景中。

总结一下:

  1. 我们使用标准HTML中的A-Frame声明WebVR场景。
  2. 我们可以从JavaScript中以编程方式添加/删除/更新场景中的A-Frame实体。
  3. 我们可以通过A-Frame插件和组件在JavaScript中使用事件处理程序创建交互。

WebVR:Veni, Look, Vici

我希望你能像我一样从这个技术演示中受益匪浅。在将这些功能(Web Worker和WebAssembly)应用于WebVR的地方, 它们也可以应用于浏览器边缘计算。

巨大的技术浪潮已经到来-虚拟现实(VR)。无论你第一次拿着智能手机时的感受如何, 第一次体验VR都会在计算的各个方面带来10倍的情感体验。距第一部iPhone才十二年。

VR已经存在了很长时间, 但是将VR带给普通用户所需的技术是通过移动革命和Facebook的Oculus Quest而不是PC革命来实现的。

互联网和开源是人类世界最伟大的奇迹之一。对于所有创建扁平化互联网的人们-我向你的勇气和冒险精神敬酒。

宣言!我们将创造世界, 因为我们拥有创造的力量。

WebVR演示

画布演示, WebVR演示, 示例代码

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?