Bootstrap

纯HTML开发VR应用:A-Frame JS

又一次看到在网上看到一个HTML5的VR街道的应用,印象比较深刻,觉得还挺有意思的。正好最近项目间隙,找点事情搞搞,所以找到一个WebVR的网页开发框架A-Frame。以下内容整理来自于踏得网的A-Frame中文教程。

简介

虚拟现实(VR)是一种技术,使用头显设备产生逼真的图像,声音和其他感觉,使得用户进入身临其境的虚拟环境。VR允许我们创建无界的世界,人们可以使用手来控制虚拟世界中的行走和互动,仿佛他们被时空传送到另一个地方。如果说互联网打破了时空界限,那么虚拟实现将打破虚拟和现实的界限。

A-Frame是一款用来构建虚拟现实(VR)的网页开发框架,属于当下流行的开发WebVR解决方案。A-Frame是完全开源的框架,核心思想来自three.js,提供声明式、可拓展、组件化的编程结构,并且支持主流VR头显。

个人理解:通过A-Frame框架,将3D、VR、AR设计中的元素进行标签化,类似html里面的标签或者元素。常见到到标签有:

  • a-scene(屏幕)
  • a-entity(实体)
  • a-assets(资源)
  • a-light(光照)
  • a-animation(动画)
  • a-camera(相机)
  • a-cursor(光标)
  • a-sound(声音)
  • a-text(文字)

有些常用的实体标签进行封装,比如正方体、圆柱体、球之类。

  • a-sky(天空/背景)
  • a-plane(地面)
  • a-box(方体)
  • a-sphere(球)
  • a-cylinder(圆柱)

有标签,就有标签的属性了。常见的属性值有:

  • position(定位:x,y,z轴值)
  • rotation(旋转:x,y,z轴角度)
  • geometry(几何数据)
  • material(表面材质)
  • scale(缩放)
  • particle-system(粒子系统)
  • color(颜色)
  • radius(半径:圆的会用)
  • height(高)
  • width(宽)

然后就是各种类型的属性值了。开发的时候就是写各种元素根据需要向屏幕中进行堆叠就可以了。是不是跟你平时做的html开发没啥区别。

后面根据你项目需要可以自己写组件,根据特殊的需求,可以引入各种各样写好的插件,满足你的天马行空的想象。

举个栗子

就拿上面提到的元素标签来几段代码,做个效果。

<html>
  <head>
    <script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>
复制代码

效果预览1

<html>
  <head>
    <script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/aframe-physics-system.min.js"></script>
  </head>
  <body>
    <a-scene physics>
      <a-box position="-1 4 -3" rotation="0 45 0" color="#4CC3D9" dynamic-body></a-box>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>
复制代码

效果预览2

<html>
  <head>
    <script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/aframe-animation-component.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/aframe-particle-system-component.min.js"></script>
    <script src="https://unpkg.com/aframe-extras.ocean@%5E3.5.x/dist/aframe-extras.ocean.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/gradientsky.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-entity id="rain" particle-system="preset: rain; color: #24CAFF; particleCount: 5000"></a-entity>
      <a-entity id="sphere" geometry="primitive: sphere"
                material="color: #EFEFEF; shader: flat"
                position="0 0.15 -5"
                light="type: point; intensity: 5"
                animation="property: position; easing: easeInOutQuad; dir: alternate; dur: 1000; to: 0 -0.10 -5; loop: true"></a-entity>
      <a-entity id="ocean" ocean="density: 20; width: 50; depth: 50; speed: 4"
                material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
                rotation="-90 0 0"></a-entity>
      <a-entity id="sky" geometry="primitive: sphere; radius: 5000"
                material="shader: gradient; topColor: 235 235 245; bottomColor: 185 185 210"
                scale="-1 1 1"></a-entity>
      <a-entity id="light" light="type: ambient; color: #888"></a-entity>
    </a-scene>
  </body>
</html>
复制代码

效果预览3

如何使用

自行运行上述代码试试。真正项目运行当然就需要web服务器了,前面引入JS,中间不断堆叠需要的实体、特效、交互、动画就可以了。不需要编译,直接预览就可以。

<!-- Production Version, Minified -->
<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<!-- Development Version, Uncompressed with Source Maps -->
<script src="https://aframe.io/releases/0.5.0/aframe.js"></script>
复制代码

遇到一个小问题,本地html文件引入同文件资源,居然还产生跨域问题。

设备和平台支持

A-Frame支持的通用平台包括:

  • 桌面电脑上的虚拟现实和头戴设备
  • 移动设备上的虚拟现实和头戴设备
  • 平面桌面设备(也就是普通电脑显示器、鼠标和键盘)
  • 智能手机(比如magic window)

一些其他的平台包括:

  • 增强现实(AR)头戴设备(比如HoloLens, Windows Mixed Reality)。
  • 移动设备上的增强现实(AR) (比如magic window)
  • AltSpaceVR,通过本地SDK

当然前端开发要关心支持那些浏览器了,webvr.rocks维护最新列表。目前

  • Firefox Nightly (Firefox 55)
  • Chromium的实验版本
  • Chrome for Android (Daydream)
  • Oculus Carmel (GearVR)
  • Samsung Internet (GearVR)
  • Microsoft Edge

A-Frame尝试通过WebVR polyfill来支持那些未实现WebVR接口的现代浏览器,但是由于不是官方支持,所以体验可能会不佳:

  • Safari for iOS
  • Chrome for Android
  • Firefox for iOS
  • Samsung Internet
  • UC Browser

对于平面的3D渲染,A-Frame支持所有实现了WebGL接口的现代浏览器:

  • Firefox
  • Chrome
  • Safari

总之,万恶的IE

Primitives

踏得网翻译是原语,百度翻译是基元。实际就是个语法糖,把一些常用的实体进行封装。方便理解学习,降低开发难度,本身也是实体。

比如 实际是 并且a-box增加属性,映射到a-entity的具体属性值中。

<a-box color="red" width="3"></a-box>
//效果跟下方相同
<a-entity geometry="primitive: box; width: 3" material="color: red"></a-entity>
复制代码

这种语法糖可以通过AFRAME.registerPrimitive(name, definition)进行注册,方法也很简单。举例:

AFRAME.registerPrimitive('a-ocean', {
  defaultComponents: {
    ocean: {},
    rotation: {x: -90, y: 0, z: 0}
  },
  mappings: {
    width: 'ocean.width',
    depth: 'ocean.depth',
    density: 'ocean.density',
    color: 'ocean.color',
    opacity: 'ocean.opacity'
  }
});
复制代码

其中,name是对应生成的新实体的名字,definition是新实体需要的参数对象。definition参数对象中包含defaultComponents和mappings,defaultComponents是新实体的默认参数,比如先指定下几何图形等等,mappings是对外暴露的属性值及在默认对象中的对应关系。

有没有一种面向对象开发中,a-entity是基础类,注册一个子类,继承父类,对外暴露特定接口的概念。

ECS架构

实体-组件-系统(entity-component-system),ECS架构是三维游戏中常见且理想的设计模式,Unity就是ECS架构的游戏引擎。A-Frame是基于three.js,并且使用了ECS架构。

ECS的基本定义包括:

  • 实体(Entities)是容器对象,用来包含组件。实体是场景中所有对象的基础。没有附加组件的实体不会渲染任何东西,类似于空的div标签。对应的是 元素和原型

  • 组件(Components)是可重用的模块或数据容器,可以依附于实体以提供外观、行为和/或 功能。组件就像即插即用的对象。所有的逻辑都是通过组件实现,并通过混合、匹配和配置组件来定义不同类型的对象。通过的HTML属性来表示。底层实现上, 组件是包含模式(schema)、生命周期处理器和方法的对象。组件通过AFRAME.registerComponent (name, definition)API来注册。

  • 系统(Systems) 为组件类提供全局范围、管理和服务。系统通常是可选的,但我们可以使用它们 来分离逻辑和数据;系统处理逻辑,组件充当数据。通过的HTML属性来表示。系统在定义上和组件类似,系统通过AFRAME.registerSystem (name, definition) API来注册。

对应aframe框架,类比HTML结构理解,就是 systems是html/body标签,Entities是div标签,Component是div标签上的style属性。

组件上的语法属性值和style的样式一样,可以添加很多。

<a-entity geometry="primitive: sphere; radius: 1.5"
          light="type: point; color: white; intensity: 2"
          material="color: white; shader: flat; src: glow.jpg"
          position="0 0 -5"></a-entity>
复制代码

JavaScript, Events 和 DOM APIs

把元素堆叠起来是显示效果,当然我们还需要控制场景中的元素为我们所用。这里还是进行的DOM操作,

查询遍历方法

querySelector()和querySelectorAll(),借鉴Jquery。

<html>
  <a-scene>
    <a-box id="redBox" class="clickable" color="red"></a-box>
    <a-sphere class="clickable" color="blue"></a-sphere>
    <a-box color="green"></a-box>
    <a-entity light="type: ambient"></a-entity>
    <a-entity light="type: directional"></a-entity>
  </a-scene>
</html>
复制代码

声明对象可以使用下面一些方式进行声明。

var sceneEl = document.querySelector('a-scene'); //指定一个对象
var redBox = sceneEl.querySelector('#redBox'); //使用ID值
var boxs = sceneEl.querySelectorAll('a-box');  //选择一类实体
var btns = sceneEl.querySelectorAll('.clickable'); //使用class值
var lights = sceneEl.querySelectorAll('[light]'); //使用属性值
var els = sceneEl.querySelectorAll('*'); //使用通配符

复制代码

对象属性操作

后续整个理解其实和正常的操作dom,使用的一样的方法和接口。

  • createElement(创建对象)
  • appendChild(追加对象)
  • removeChild(删除对象)
  • setAttribute(设置属性)
  • removeAttribute(删除属性)

事件和侦听器

和浏览器事件相同,增加的事件可以通过.emit向外发射,.addEventListener进行侦听,.removeEventListener删除侦听。举例,空间里面两个对象碰到了就叫一声(emit),空间里面管理员听到了就会去处理。addEventListener就是增加管理员,里面代码是听到叫声要操作的事情。

entityEl.emit('physicscollided', {collidingEntity: anotherEntityEl}, false); //碰了就叫一声

function collisionHandler (event) {
  console.log('Entity collided with', event.detail.collidingEntity);
}); //要做的时事情

entityEl.addEventListener('physicscollided', collisionHandler); //放侦听器处理
entityEl.removeEventListener('physicscollided', collisionHandler); //删除侦听器

复制代码

父子关系与坐标转换

A-Frame中元素也存在父子关系的嵌套,并且父子元素之间的空间关系也是相互影响的。

<a-scene>
  <a-box>
    <a-sphere></a-sphere>
    <a-light></a-light>
  </a-box>
  <a-entity id="foo" position="1 2 3">
    <a-entity id="bar" position="2 3 4"></a-entity>
  </a-entity>
</a-scene>
复制代码

其中id为bar的实体的实际空间坐标就变成了(3, 5, 7)。最外层坐标叫世界坐标,元素里面的坐标是相对于父坐标的坐标。

可视化查看器和开发工具

A-Frame查看器是一个用来检测和修改场景元素的可视化工具。使用 + + i组合快捷键来打开/关闭查看器,使用该工具,我们可以:

  • 使用句柄和助手拖动、旋转和缩放实体
  • 使用小部件(widgets)调整实体的组件及其属性
  • 立即看到更改结果,而无需在代码和浏览器之间来回切换

查看器类似于浏览器的DOM查看器,但面向的是3D上下文和A-Frame。

生命周期

  • init 初始化组件时调用一次。用于设置初始状态和实例化变量。
  • update 在组件初始化和任何组件属性更新(例如,通过setAttribute)修改属性)时被调用来更新该实体。
  • remove 在组件被从实体中删除时(比如,通过removeAttribute)或者当实体从场景中分离时被调用。用于撤消以前所有的实体修改。
  • tick 在每个场景渲染循环被调用。用于连续的改变或检查。
  • play 每当场景或实体播放来添加任意背景或动态行为时被调用。组件初始化时也会被调用一次。用于启动或恢复动态行为。
  • pause 每当场景或实体暂停来删除任意背景或动态行为时被调用。当组件从实体中移除或实体脱离场景时也会被调用。用于暂停动态行为。
  • updateSchema 当组件的任意属性更新时被调用。可用来动态修改模式(schema)。

文末

本文算是一个初期的入门理解,更加深入的理解肯定要实战中去增强理解,好多问题只有在多次实战运用之后才会又更深入的了解。而且像这种3D内容的开发,更多的其实是空间设计能力,这个才是重点。

;