翻譯 | 使用A-Frame打造WebVR版《我的世界》


我是 Kevin Ngo,一名就職於 Mozilla VR 團隊的 web 虛擬現實開發者,也是 A-Frame 的核心開發人員。今天,我們來看看如何使用 A-Frame 構建一個夠在 HTC Vive、Oculus Rift、Samsung GearVR、Google Cardboard、桌面設備以及移動設備上運行的、支持空間追蹤(room-scale)技術的 WebVR 版《我的世界》示例。該示例基於 A-Frame,且僅使用 11 個 HTML 元素!

A-Frame

幾年前,Mozilla 發明並開發了 WebVR —— 一套在瀏覽器中創造身臨其境 VR 體驗的 JavaScript API —— 並將其發布在一個實驗版本的 Firefox 瀏覽器中。此后,WebVR 得到了 Google、Microsoft、Samsung 以及 Oculus 等其他公司的廣泛支持。而現在,WebVR 更是在短短幾個月內就被內嵌在發行版的 Firefox 瀏覽器中,並被設置為默認開啟!

為什么會誕生 WebVR?Web 為 VR 帶來了開放性;在 Web 上,內容並不由管理員所控制,用戶也不被關在高高的圍牆花園(walled garden)中。Web 也為 VR 帶來了連通性;在 Web 上,我們能夠在世界中穿梭 —— 就像我們點擊超鏈接在頁面見穿梭一樣。隨着 WebGL 的成熟以及諸如 Web Assembly 和 Service Workers 規范的提出,WebVR 已經准備好了。

Mozilla VR 團隊創造了 A-Frame 框架來為 WebVR 生態系統拋磚引玉,該框架給予開發者構建 3D 和 VR 世界的能力。



A-Frame 官方網站首頁


A-Frame 是一個構建虛擬現實體驗設的 web 框架,它基於 HTML 和實體組件范式(the Entity-Component pattern)。HTML 是所有計算機語言中最易理解的語言,這使得任何人都能快速上手 A-Frame。下面是一個使用 HTML 搭建的完整的 3D 和 VR 場景,它能夠在諸如桌面設備和移動設備等任何 VR 平台運行:

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

<a-scene>
  <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
  <a-box position="-1 0.5 -3" rotation="0 45 0" width="1" height="1" depth="1" color="#4CC3D9"></a-box>
  <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>

在 CodePen 中打開

就是這樣!只用使用一行 HTML( )即可搞定 3D 和 VR 樣板代碼搭建 ,包括:canvas、場景、渲染器、渲染循環、攝像機以及 raycaster。然后,我們可以通過使用添加子元素的方式來為場景添加對象。無需構建,就只是一個簡單的、可隨意拷貝粘貼的 HTML 文件。



我們還可以動態查詢和操作 A-Frame 的 HTML,就像使用標准 JavaScript 和 DOM APIs (例如 querySelector、getAttribute、addEventListener、setAttribute)那樣。

// 使用 `querySelector` 查詢場景圖像。
var sceneEl = document.querySelector('a-scene');
var boxEl = sceneEl.querySelector('a-box');

// 使用 `getAttribute` 獲得實體的數據。
console.log(box.getAttribute('position'));
// >> {x: -1, y: 0.5, z: -3}

// 使用 `addEventListener` 監聽事件。
box.addEventListener('click', function () {
  // 使用 `setAttribute` 修改屬性。
  box.setAttribute('color', 'red');
});


而且,因為這些只是 HTML 和 JavaScript,因此 A-Frame 和許多現存的框架和庫兼容良好:



兼容 d3、Vue、React、Redux、jQuery、Angular


盡管 A-Frame 的 HTML 看起來比較簡單,但是 A-Frame 的 API 卻遠遠比簡單的 3D 聲明強大。A-Frame 是一個實體組件系統(ECS)框架,ECS 在游戲開發中是一種流行的模式,值得注意的是 ECS 也被 Unity 引擎所使用。其概念包括:

  • 在場景中,所有的對象都是實體(entities),空對象本身什么也不能做,類似空 <div>。A-Frame 使用 HTML 元素在 DOM 中表示實體。
  • 接下來,我們在實體中插入組件(components) 來提供外觀、行為和功能。在 A-Frame 中,組件被注冊在 JavaScript 中,並且可以被用來做任何事情。它們可使用完整的 three.js 和 DOM APIs。組件注冊后,可以附加在 HTML 實體上。

ECS 的優勢在於它的可組合性;我們可以混合和搭配這些可復用的組件來構建出更復雜的 3D 對象。A-Frame 更上一層樓,將這些組件聲明化,並使其作為 DOM 的一部分,就像我們待會在《我的世界》示例中看到那樣。

示例骨架

現在來關注我們的示例。我們將搭建一個基本的 VR 立體像素制作器(voxel builder),它主要用於支持位置追蹤(positional tracking)和追蹤控制器(tracked controllers)的空間追蹤 VR 設備(例如 HTC Vive 及 Oculus Rift + Touch)。

我們會從 HTML 骨架開始。如果你想要快速瀏覽(完整的 11 行 HTML),點擊這里到 GitHub 查看源代碼

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

<body>
  <a-scene>
  </a-scene>
</body>

添加地面

<a-plane><a-circle> 都是常被用作添加地面的圖元,不過我們會使用 <a-cylinder> 來更好地配合控制器完成燈光計算工作。圓柱(cylinder)的半徑為 30 米,待會我們要添加的天空將會和這個半徑值匹配起來。注意 A-Frame 中的單位是米,以匹配 WebVR API 返回的現實世界中的單位。

地面的紋理部署在 https://cdn.aframe.io/a-painter/images/floor.jpg。我們將紋理添加進項目中,並使用該紋理制作一個扁的圓柱實體。

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

<a-scene>
  <a-cylinder id="ground" src="https://cdn.aframe.io/a-painter/images/floor.jpg" radius="32" height="0.1"></a-cylinder>
</a-scene>

在 CodePen 中打開

預加載資源

通過 src 屬性指定的 URL 資源將在運行時加載。

由於網絡請求會對渲染的性能產生負面影響,所以我們可以預加載紋理以保證資源被下載完成前不進行渲染工作,預加載可以通過資源管理系統(asset management system)來完成。

我們將 <a-assets> 置入 <a-scene> 中,將資源(例如圖片、視頻、模型及聲音等)置入 <a-assets> 中,並通過選擇器(例如 #myTexture)將資源指向我們的實體。

讓我們將地面紋理移動到 <a-assets> 中,使用 <img> 元素來預加載它:

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

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
  </a-assets>

  <a-cylinder id="ground" src="#groundTexture" radius="32" height="0.1"></a-cylinder>
</a-scene>

在 CodePen 中打開

添加背景

讓我們使用 <a-sky> 元素<a-scene> 添加一個 360° 的背景。<a-sky> 是一個在內部粘貼材質的巨大 3D 球體。就像普通圖片一樣,<a-sky> 可以通過 src 屬性接受圖片地址。最終我們將可以使用一行 HTML 代碼實現身臨其境的 360° 圖片。稍后你也可以在 Flickr 球面投影圖片池(需翻牆)中選擇一些 360° 圖片來做練習。

我們可以添加普通的顏色背景(例如 <a-sky color="#333"></a-sky>)或漸變,不過這次讓我們來添加一張背景紋理圖片。該圖片被部署在 https://cdn.aframe.io/a-painter/images/sky.jpg。我們所使用的圖片是一張適用於半球體的圖片,所以首先我們需要將剛剛的球體使用 theta-length="90" 水平截成半球體,另外我們將球的半徑設置為 30 米以匹配地面。

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

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
    <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
  </a-assets>
  
  <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>

  <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>
</a-scene>

在 CodePen 中打開

添加體素

在我們的 VR 應用中,體素(voxels)的寫法類似 <a-box>,但會添加一些自定義的 A-Frame 組件。不過讓我們先大致了解實體-組件范式,來看看像 <a-box> 這樣的圖元是怎樣合成的。

在這個部分,我們將會對若干 A-Frame 組件的實現做一些深入探討。在實踐中,我們經常會通過已由 A-Frame 社區開發人員編寫好的 HTML 來使用組件,而不是從頭構建它們。

實體-組件范式

在 A-Frame 場景中的每一個對象都是 <a-entity>,其本身什么也不能做,就像一個空 <div> 一樣。我們將組件(不要和 Web Components 或 React Components 混淆)插入實體來給予其外觀、行為和邏輯。

對於一個盒子來說,我們會為其配置及添加 A-Frame 的基礎幾何組件材質組件。組件使用 HTML 屬性來表示,組件屬性默認使用類似 CSS 樣式的表示方法來表示。下面是一個 <a-box> 的基礎組件拆解寫法,可以看到 <a-box> 事實上包裹了若干組件:

<a-box color="red" depth="0.5" height="0.5" shader="flat" width="0.5"></a-box>

<a-entity geometry="primitive: box; depth: 0.5; height: 0.5; width 0.5"
          material="color: red; shader: standard"></a-entity>

使用組件的好處是它們的具有可組合性。我們可以通過混合和搭配一堆已有的組件來構造出各種各樣的對象。

在 3D 開發中,我們可能構建出的對象類型在數量和復雜性上是無限的,因此我們需要一個簡便的、全新的、非傳統繼承式的對象定義方法。與 2D web 相比,我們不再拘泥於使用一小撮固定的 HTML 元素並將它們嵌套在很深的層次結構中。

隨機顏色組件

A-Frame 中的組件由 JavaScript 定義,它們可使用完整的 three.js 和 DOM APIs,它們可以做任何事。所有的對象都由一捆組件來定義。

現在將剛剛所描述的模式付諸實踐,通過書寫一個 A-Frame 組件,為我們的盒子設置隨機顏色。組件通過 AFRAME.registerComponent 注冊,我們可以定義 schema(組件的數據)以及生命周期方法(組件的邏輯)。對於隨機顏色組件,我們並不需要設置 schema,因為它不能被配置。但我們會定義一個 init 處理函數,該函數會在組件首次附加到它的實體時被調用。

AFRAME.registerComponent('random-color', {
  init: function () {
    // ...
  }
});

對於隨機顏色組件,我們的意圖是為其附加的實體設置隨機顏色。在組件的方法中,可以使用 this.el 訪問實體的引用。

為了使用 JavaScript 來改變顏色,我們使用 .setAttribute() 來設置材質組件的顏色屬性。A-Frame 只引入了少數 API,大多數 API 和原生 web 開發 API 保持一致。點此詳細了解如何在 A-Frame 中使用 JavaScript 和 DOM API

我們還需要將 material 組件添加到預先初始化組件列表中,以保證材質不會被 material 組件覆蓋掉。

AFRAME.registerComponent('random-color', {
  dependencies: ['material'],

  init: function () {
    // 將材質組件的顏色屬性設置為隨機顏色
    this.el.setAttribute('material', 'color', getRandomColor());
  }
});

function getRandomColor() {
  const letters = '0123456789ABCDEF';
  var color = '#';
  for (var i = 0; i < 6; i++ ) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}

在組件被注冊后,我們可以直接使用 HTML 來鏈接該組件。A-Frame 框架中的所有代碼都是對 HTML 的擴展,而且這些擴展可以用於其他對象和其他場景。很棒的是,開發者可以寫一個向對象添加物理元素的組件,使用這個組件的人甚至不會察覺到 JavaScript 在他的場景中加入了這個物理元素!

注意力回到剛剛的盒子實體,將 random-color 作為 HTML 屬性插入到 random-color 組件中。我們將組件保存為一個 JS 文件,然后在場景代碼之前引用它:

<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/random-color.js"></script>

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
    <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
  </a-assets>
  
  <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>

  <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>
  
  <!-- 隨機顏色的盒子 -->
  <a-entity geometry="primitive: box; depth: 0.5; height: 0.5; width 0.5"
            material="shader: standard"
            position="0 0.5 -2"
            random-color></a-entity>
</a-scene>

在 CodePen 中打開

組件可以插入到任何實體中,但並不需要像在傳統繼承模式中那樣創建或擴展類。如果我們想在類似 <a-shpere><a-obj-model> 中附加組件,直接加就是了!

<!-- 在其他實體上重用並附加隨機顏色組件 -->
<a-sphere random-color></a-sphere>
<a-obj-model src="model.obj" random-color></a-obj-model>

如果我們想要將這個組件分享給他人使用,也沒問題。我們可以在 A-Frame 倉庫中獲取 A-Frame 生態系統中許多便利的組件,這類似 Unity 的 Asset Store。如果我們使用組件開發應用程序,那么就應當保證我們的代碼在內部是模塊化和可重用的!

對齊組件

我們將使用 snap 組件來將盒子對齊到網格以避免它們重疊。我們不會深入到該組件的實現原理,不過你可以看看 snap 組件的源代碼(20 行 JavaScript 代碼)。

將 snap 組件附加到盒子實體上,讓盒子每半米對齊,同時使用 offset 來使盒子居中:

<a-entity
   geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
   material="shader: standard"
   random-color
   snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"></a-entity>

現在,我們有了一個由一捆組件構成的盒子實體,該實體可以用來描述我們場景中的所有體素(磚塊)。

Mixins

我們可以創建 mixin 來定義可復用的組件集合。

與使用 <a-entity> 為場景添加一個對象不同,我們使用 <a-mixin> 來創建可復用的體素,使用它們就像使用預設實體一樣。

<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/random-color.js"></script>
<script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/snap.js"></script>

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
    <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
    <a-mixin id="voxel"
       geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
       material="shader: standard"
       random-color
       snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"></a-mixin>
  </a-assets>

  <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>

  <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>
  
  <a-entity mixin="voxel" position="-1 0 -2"></a-entity>
  <a-entity mixin="voxel" position="0 0 -2"></a-entity>
  <a-entity mixin="voxel" position="0 1 -2">
    <a-animation attribute="rotation" to="0 360 0" repeat="indefinite"></a-animation>
  </a-entity>
  <a-entity mixin="voxel" position="1 0 -2"></a-entity>
</a-scene>

在 CodePen 中打開

隨后我們使用 mixin 添加了若干體素:

<a-entity mixin="voxel" position="-1 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 1 -2">
  <a-animation attribute="rotation" to="0 360 0" repeat="indefinite"></a-animation>
</a-entity>
<a-entity mixin="voxel" position="1 0 -2"></a-entity>

接下來,我們將通過使用追蹤控制器根據用戶交互來動態創建體素。讓我們開始向程序中添加一雙手吧。

添加手部控制器

添加 HTC Vive 或 Oculus Touch 追蹤控制器非常簡單:

<!-- Vive -->
<a-entity vive-controls="hand: left"></a-entity>
<a-entity vive-controls="hand: right"></a-entity>

<!-- Rift -->
<a-entity oculus-touch-controls="hand: left"></a-entity>
<a-entity oculus-touch-controls="hand: right"></a-entity>

我們將使用抽象的 hand-controls 組件來同時兼容 Vive 和 Rift 的控制,它提供基本的手模型。左手負責移動位置,右手負責放置磚塊。

<a-entity id="teleHand" hand-controls="left"></a-entity>
<a-entity id="blockHand" hand-controls="right"></a-entity>

為左手添加瞬移功能

我們將為左手增加瞬移的能力,當按住左手控制器按鈕時,從控制器顯示一條弧線,松開手時,瞬移到弧線末端的位置。在此之前,我們已經自己寫了一個實現隨機顏色的 A-Frame 組件。

但也可以使用社區中已有的開源組件,然后直接通過 HTML 使用它們!

對於瞬移來說,有一個來自於 @fernandojsg 的瞬移控制組件。遵循 README,我們使用 <script> 標簽引入 teleport-controls 組件,並將其附加到控制器實體上。

<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-teleport-controls@0.2.x/dist/aframe-teleport-controls.min.js"></script>

<!-- ... -->

<a-entity id="teleHand" hand-controls="left" teleport-controls></a-entity>
<a-entity id="blockHand" hand-controls="right"></a-entity>

隨后我們來配置 teleport-controls 組件,將瞬移的 type 設置為弧線。默認來說,teleport-controls 的瞬移只會發生在地面上,但我們也可以指定 collisionEntities 通過選擇器來允許瞬移到磚塊地面上。這些屬性是 teleport-controls 組件創建的 API 的一部分。

<a-entity id="teleHand" hand-controls="left" teleport-controls="type: parabolic; collisionEntities: [mixin='voxel'], #ground"></a-entity>

就是這樣!只要一個 script 標簽和一個 HTML 屬性,我們就能瞬移了。A-Frame 倉庫中可以找到更多很酷的組件。

為右手添加體素生成器功能

在 2D 應用程序中,對象內置了處理點擊的能力,而在 WebVR 中對象並沒有這樣的能力,需要我們自己來提供。幸運的是,A-Frame 擁有許多處理交互的組件。VR 中用於類似光標點擊的場景方法是使用 raycaster,它射出一道激光並返回激光命中的物體。然后我們通過監聽交互事件及查看 raycaster 來獲得命中點信息。

A-Frame 提供基於注視點的光標(注:就像 FPS 游戲的准心那樣),可以利用此光標點擊正在注視的物體,但也有可用的控制器光標組件來根據 VR 追蹤控制器的位置發射激光,就像剛剛使用 teleport-controls 組件那樣,我們通過 script 標簽將 controller-cursor 組件引入,然后附加到實體上。這次輪到右手了:

<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-teleport-controls@0.2.x/dist/aframe-teleport-controls.min.js"></script>
<script src="https://unpkg.com/aframe-controller-cursor-component@0.2.x/dist/aframe-controller-cursor-component.min.js"></script>

<!-- ... -->

<a-entity id="teleHand" hand-controls="left" teleport-controls="type: parabolic; collisionEntities: [mixin='voxel'], #ground"></a-entity>
<a-entity id="blockHand" hand-controls="right" controller-cursor></a-entity>

現在當我們按下追蹤控制器上的按鈕時,controller-cursor 組件將同時觸發控制器和交互實體的 click 事件。A-Frame 也提供了諸如 mouseentermouseleave 這樣的事件。事件包含了用戶交互的詳細信息。

這賦予了我們點擊的能力,但我們還得寫一些響應點擊事件處理生成磚塊的邏輯。可以使用事件監聽器及 document.createElement 來完成:

document.querySelector('#blockHand').addEventListener(`click`, function (evt) {
  // 創建一個磚塊實體
  var newVoxelEl = document.createElement('a-entity');

  // 使用 mixin 來將其變為體素
  newVoxelEl.setAttribute('mixin', 'voxel');

  // 使用命中點的數據來設置磚塊位置。
  // 上文所述的 `snap` 組件是 mixin 的一部分,它將會把磚塊對齊到最近的半米
  newVoxelEl.setAttribute('position', evt.detail.intersection.point);

  // 使用 `appendChild` 添加到場景中
  this.appendChild(newVoxelEl);
});

為了概括性地處理在命中點創建實體這樣的需求,我們創建了 intersection-spawn 組件,該組件接受任何事件和屬性列表的配置。我們不會詳細討論其實現,但你可以在 GitHub 上查看這個簡單的 intersection-spawn 組件的源碼。我們將 intersection-spawn 的能力附加到右手上:

<a-entity id="blockHand" hand-controls="right" controller-cursor intersection-spawn="event: click; mixin: voxel"></a-entity>

現在當我們點擊時,就可以生成體素了!

添加移動設備和桌面設備支持

我們通過組合組件了解到了如何構建一個自定義類型的對象(例如,一個具有點擊功能和點擊時生成磚塊的手部控制器)。組件的好處之一是它們可以在不同的上下文中被重用。我們將 intersection-spawn 組件和基於注視點的 cursor 組件結合起來,便可以在一點都不改變組件的情況下,實現在移動設備和桌面設備中生成磚塊的功能了。

<a-entity id="blockHand" hand-controls="right" controller-cursor intersection-spawn="event: click; mixin: voxel"></a-entity>

<a-camera>
  <a-cursor intersection-spawn="event: click; mixin: voxel"></a-cursor>
</a-camera>

試試看

在 GitHub 上查看源碼

我們的 VR 體素構建器最終使用 11 個 HTML 元素實現。我們可以在桌面或移動設備上預覽它。在桌面設備上,我們可以通過拖動和點擊來生成磚塊;在移動設備上,我們可以平移設備和點擊屏幕來生成磚塊。

<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-teleport-controls@0.2.x/dist/aframe-teleport-controls.min.js"></script>
<script src="https://unpkg.com/aframe-controller-cursor-component@0.2.x/dist/aframe-controller-cursor-component.min.js"></script>
<script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/random-color.js"></script>
<script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/snap.js"></script>
<script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/intersection-spawn.js"></script>

<body>
  <a-scene>
    <a-assets>
      <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
      <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
      <a-mixin id="voxel"
         geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
         material="shader: standard"
         random-color
         snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"
      ></a-mixin>
    </a-assets>

    <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>

    <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>

    <!-- Hands. -->
    <a-entity id="teleHand" hand-controls="left" teleport-controls="type: parabolic; collisionEntities: [mixin='voxel'], #ground"></a-entity>
    <a-entity id="blockHand" hand-controls="right" controller-cursor intersection-spawn="event: click; mixin: voxel"></a-entity>

    <!-- Camera. -->
    <a-camera>
      <a-cursor intersection-spawn="event: click; mixin: voxel"></a-cursor>
    </a-camera>
  </a-scene>
</body>

在 CodePen 中打開

如果你有 VR 頭盔(例如 HTC Vive、Oculus Rift + Touch),那么可以找一個支持 WebVR 的瀏覽器並打開示例。

如果你想使用桌面或移動設備觀看 VR 是什么樣的,可以查看錄制好的 VR 動作捕捉和手勢演示

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、當當開售。

滬江Web前端上海團隊招聘【Web前端架構師】,有意者簡歷至:zhouyao@hujiang.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM