新しいWebVRフレームワークA-Frame入門



# とりあえずサンプル！
A-Frameを用いたWebVRサンプルとして，うさぎ(stanford bunny)がぴょんぴょんするサンプルを作りました＞＜

http://cslroot.github.io/a-frame-examples/#aframe_demo_bunny

このサンプルは1行もjavascriptを記述していません！ すごい！ 


----------------------

混沌としたWebVR界隈に突如現れたWebVRフレームワーク A-Frame．
さっそく試してみたので，本記事ではその使い方を解説してみるよ！

この記事には，
1. ステップバイステップで一通りの機能を使いながらWebVRシーンを作成する方法と，
2. 各機能や要素の詳細な説明
が書かれています．


公式サイト: https://aframe.io/
ドキュメント: https://aframe.io/docs/guide/


# はじめに

## A-Frameってどういうもの？
HTMLのタグとしてシーンを記述できるWebVR用のフレームワークです．
Three.jsをベースとして開発されており，WebVR対応ブラウザではデフォルトでVR表示が可能となっています．


[Oculus Rift Advent Calendar 2015](http://qiita.com/advent-calendar/2015/oculus-rift)の23日分でも[k0rinさんがA-Frameを紹介してくださっています](http://framesynthesis.jp/tech/2015/12/aframe/)．こちらもチェック！


## 特徴
次のような特徴を持っています．

* WebGLを知らなくてもhtmlタグでシーンを記述できる
* WebVRにデフォルトで対応している
* レスポンシブなVRサイトが構築できる（Oculus Rift, CardBoard）
* WebVRに特化した機能（注視点を自動選択する，など）を備える
* MozVRが開発している（Firefox系）
* なにげに物理ベースレンダリング(PBR)のマテリアル指定を採用
* [MITライセンスでソース公開されている](https://github.com/aframevr/aframe/)


## 日本語関連の情報まとめ

* [Codezine Mozilla、WebVRフレームワーク「A-Frame」を公開、WebGLの知識がなくても簡単にWeb上でVRを構築可能](http://codezine.jp/article/detail/9173)
* [EngadgetJP Oculus対応のVRサイトをHTML感覚で作れるフレームワーク「A-Frame」をMozillaが公開](http://japanese.engadget.com/2015/12/17/oculus-vr-html-a-frame-mozilla/)
* [WebGL総本山 こりゃとんでもねえぞ！！ MozVR チーム純正の WebVR 専用フレームワーク A-Frame がすごい！](http://webgl.souhonzan.org/entry/?v=0454)
* [1行のコードでWebサイトにVRを。Mozillaより、Web VRライブラリ「A-Frame」がリリース](http://www.moguravr.com/a-frame/)


# 環境
## 対応ブラウザ，対応デバイス
* ブラウザ
    * FirefoxかWebVR有効版のChoromiumの場合はVR表示が可能です（対応デバイスが接続されている場合）
        * ※ 通常のChromeでもOculusでの表示はできませんが，WebGL表示は可能です．
* 対応デバイス
    * Oculus Rift
        * Windows PC ONLY．osxやlinuxはoculus側の対応ストップ中につき．
    * CardBoard系のホルダ（iPhone）
        * (Android対応途中．テクスチャが真っ黒になるなど問題あり．Nexus6だと行ける？)
* WebVR APIの対応状況にしたがって，対応デバイスも増える予定らしいです！


## 準備

次のアドレスから，ボイラープレート（テンプレート）がダウンロードできます．
https://github.com/aframevr/aframe-boilerplate/archive/master.zip


# 使い方
以下，https://aframe.io/docs/guide/ を元に日本語で解説してみました．

### aframe.jsを読込む
次のいずれかの方法が使えます

* `<script>`タグに`https://aframe.io/releases/latest/aframe.min.js`を指定する．
    * ※開発版は`https://aframe.io/releases/latest/aframe.js`を使います．
* aframe.jsをダウンロードして使う

読み込めたら，早速書いていきましょう！

```index.html
<!doctype html>
<html>
  <head>
    <title>My first VR site</title>
    <script src="https://aframe.io/releases/latest/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <!-- ここにシーンに追加するオブジェクトを記述していきます -->
    </a-scene>
  </body>
</html>
```

### オブジェクトの操作

#### シーンへオブジェクトを追加する

例えば次のようなコードでオブジェクト（ここでは直方体）をシーンへ追加します．

```html
<a-scene>
  <a-cube></a-cube>
</a-scene>
```
<img width="731" alt="test_cube" src="https://qiita-image-store.s3.amazonaws.com/0/18711/a5b7f84b-8de5-bc61-9eea-08064b73dadc.png">

これだけで，マウスでグリグリ動かせますし，WASDキーで移動ができます．また，VRHMDデバイスを持っていればVR体験を実現できます．
直方体(`<a-cube>`)以外にも，平面(`<a-plane>`)，画像(`<a-image>`)，動画(`<a-video>`)などが用意されています．


#### 位置，大きさ，色などを変更する
通常のHTMLタグと同様に要素の属性で指定します．
例えば直方体の場合は，幅，高さ，奥行きが3x4x5の板のような赤い直方体にしたい場合は，次のようにします：

```html
<a-cube width="1.5" height="3.0" depth="0.5" color="red"></a-cube>
```


画像や動画の場合は，`src`でURL指定すれば動きます．
各要素固有の属性があるので，適宜ドキュメントで確認しましょう．（簡単なまとめを後述）


#### グルーピング；オブジェクト階層（Hierarchy）を構築する
各オブジェクトは，入れ子にすることでグループ化できます．

```html
<a-entity id="tree" position="30 12 86">
  <a-sphere color="green" radius="0.5" position="0 1.5 0"></a-sphere>
  <a-cylinder color="brown" radius="0.2" height="1" position="0 0.5 0"></a-cylinder>
</a-entity>
```

### 陰影をつける（シェーディング）
現在のままでは，真っ赤なオブジェクトで陰影がわかりません．ライトやマテリアルを設定して，シェーディング表示になるようにしてみましょう．

ライトを追加するには，`<a-light>`を使用します．
ライトは，Three.jsと同様に，ambient, directional, point, spot, hemisphereといった種類があります．


#### デフォルトライト
シーンにライト要素が1つも無い場合は，「白の環境光」と「白の直接光 intensity 0.2, position -1 2 1」がデフォルトライトとして設定されます．
このデフォルトライトは，自分でライトを記述した場合は，設定されません．


### カメラの設定

現時点では，1つのカメラを前提としており，複数のカメラを切り替えるようなことは考慮されていません．

#### デフォルトカメラ
デフォルトのカメラ位置は`0 1.8 4`である点に注意が必要です．
これは，`0 0 0`の位置に配置されたオブジェクトが見えることと，VR HMD着用時に自然な高さになることを目的としています．

また，デフォルトのFOV(Field of View: 視野角)は，`80`度となっておりVRHMDのFOVに近いものとなっています．
一般的な3D表示のデフォルトと比べてかなり広角ですので，端がかなり歪んだように（極端なパースがかかった）表示になりますね．


### アニメーション
https://aframe.io/docs/core/animation.html

アニメーションさせるには`<a-animation>`要素を入れ子にして追加します．

```html
<a-animation attribute="rotation" from="0 0 0" to="0 360 0" repeat="indefinite" easing="linear"></a-animation>
</a-cube>
```

アニメーションは，[w3cのweb animation の仕様](http://www.w3.org/TR/web-animations/)におおよそ従っているようです．
内部的には[tween.js](https://github.com/tweenjs/tween.js/)を使っているようです．


### LookAt

この機能は，任意のオブジェクトが自動的に，対象要素を自動追従する仕組みです．

1. 追従先の対象にidを付与する（例：`id="target"`）
2. 追従して動くオブジェクトに，look-at で追従先のidを指定する（`look-at="#target"`）


### モデルロード `<a-model>`

Collada(.dae)とOBJファイルのロードに対応しています．
（今後glTFにも対応予定とのことです）

```html
<!-- Colladaモデルの読み込み -->
<a-model src="https://aframe.io/aframe/examples/_models/tree1/tree1.dae"></a-model>

<!-- Objモデルの読み込み -->
<a-model src="https://cslroot.github.io/a-frame-examples/assets/models/bunny_with_normals.obj"></a-model>
```


※法線なしのモデルをロードした場合，法線計算を実行しないとライティングが正しく表示しません．対応手段については調査中です．


### 注視点によるインタラクティブ操作
https://aframe.io/docs/components/cursor.html

カメラに`cursor`属性を付与することで利用でいます．

内部的には，レイキャストによってオブジェクトクリックイベントを発火させてくれます．
DOMのイベントリスナで`'click'`イベントのコールバックを登録してください．

また，対象要素の子要素として，`<a-mouseentry>`や`<a-mouseleave>`を指定することで，カーソルが乗った時，離れた時の挙動も指定することができます．

カーソルが反応したオブジェクトだけではなく，`target`属性でidを指定することで，連動して動かすことも可能です．

```html
    <a-camera position="0 1.8 4"
      cursor-visible="true"
      cursor-scale="2"
      cursor-color="#4CC3D9"
      cursor-offset="2"
      cursor-maxdistance="100"
      cursor-opacity="0.5">
    </a-camera>

    <a-cube id="target1" color="#666" position="-1 0 0">
      <a-mouseenter color="#066"></a-mouseenter>
      <a-mouseenter target="#target2" color="#066" position="1 1 0"></a-mouseenter>

      <a-mouseleave color="#666"></a-mouseleave>
      <a-mouseleave target="#target2" color="#666" position="1 0 0"></a-mouseleave>
    </a-cube>

    <a-cube id="target2" color="#666" position="1 0 0">
      <a-mouseenter color="#606"></a-mouseenter>
      <a-mouseenter target="#target1" color="#606" position="-1 1 0"></a-mouseenter>

      <a-mouseleave color="#666"></a-mouseleave>
      <a-mouseleave target="#target1" color="#666" position="-1 0 0"></a-mouseleave>
    </a-cube>
```

※ 相対値で指定したいですが，無理っぽいですね．


#### 一定時間カーソルを合わせた時に反応を返すには

fuseというプロパティもあり，これをONにすると，カーソルを一定時間オブジェクトに載せ続けるとイベントが発生するというVR HMD利用時に特に便利です．


### テンプレート機能
https://aframe.io/docs/core/templates.html

いくつかのプリミティブをまとめて（グループ化），1つのテンプレートとして使用する機能があります．
これは，以下のように，`<template>`要素の子要素として，テンプレート化したいプリミティブを記述することで実現します．


```html
<template is="a-template" element="a-lot-of-cubes">
  <a-cube color="red" depth="1" height="1" width="1" position="-1 0 0"></a-cube>
  <a-cube color="blue" depth="1" height="1" width="1" position="0 1 0"></a-cube>
  <a-cube color="green" depth="1" height="1" width="1" position="1 0 0"></a-cube>
</template>
```

定義したテンプレートを使用するには，`element`属性で指定した名前を使います．
上の例では，3つの（赤青緑の）立方体を`a-lot-of-cubes`という名前の要素として定義していますので，利用するには`<a-lot-of-cubes>`要素を用います．

```html
<a-lot-of-cubes></a-lot-of-cubes>
<a-lot-of-cubes position="10 0 0"></a-lot-of-cubes>
```

このようにテンプレート化しておくことで，再利用性が増し，複数個の要素を配置するのが簡単になります．

単純にグループ化するだけでなく，各プリミティブで共通する値を持たせるために，親`<template>`要素の属性値を継承するような指定も可能です．
以下の例では，子cubeのサイズと位置を，`<template>`要素の`size`属性の値を継承するような指定をしています．


```html
<template is="a-template" element="cubes-inherit" size="1">
  <a-cube color="red" depth="${size}" height="${size}" width="${size}" position="-${size} 0 0"></a-cube>
  <a-cube color="green" depth="${size}" height="${size}" width="${size}" position="-${size} ${size} 0"></a-cube>
  <a-cube color="blue" depth="${size}" height="${size}" width="${size}" position="0 ${size} 0"></a-cube>
</template>

<cubes-inherit size="2"></cubes-inherit>
<cubes-inherit position="10 0 0"></cubes-inherit>
```

※うっかりすると要素名が被ってしまうので，ユニークな名前にしたほうが良さそうです．


--------------
## Entity-Componentモデル

これまで説明してきた各要素は，Entity-Componentパターンで実現されています．
簡単に理解しておきましょう．

例えば，直方体を作るには`<a-cube>`要素を使いましたが，このcubeはgeometryコンポーネントとmaterialコンポーネントから構成されています．
実のところ，上述の`<a-cube>`は`<a-entity>`という汎用要素のラッパーとなっていて

```html
<a-cube width="3" color="red"></a-cube>
```
↑と↓は等価です．

```html
<a-entity geometry="primitive: box; width: 3" material="color: red"></a-entity>
```

htmlの`width`属性は，`geometry`コンポーネントの`width`プロパティと対応しています．


* *エンティティ*は，シーンに配置する汎用のオブジェクトです（プレイヤとか敵とか）．
* *コンポーネント*は，エンティティの振る舞いや機能を決める特性から構成されています．

エンティティは複数のコンポーネントを持つことができ，エンティティの振る舞いは実行時に，コンポーネントを付与したり削除したりすることで変化させられます．

つまりエンティティ（いままで`<a-XXXX>`タグで扱ってきたもの）は，その共通コンポーネントの組合せで作成されています．


詳細は省きますが，独自のComponentを作成することもできますので，機能の拡充が期待できます．
https://github.com/ngokevin/aframe-component-boilerplate



## mixinとは
mixinによって，Componentプロパティを事前定義して再利用することができます．
適当なidを振った`<a-mixin>`要素を`<a-assets>`に記述しておくと，そのidを使ってオブジェクトを定義することができます．

利用したい時は，'<a-mixin>'要素にふったidを，`mixin`属性に記述するだけです．

複数同時指定も可能ですが，重複する内容が定義されていた場合は，後から書いた方が有効です．次のサンプルでは，redとblueの両方が指定されていますが，blueが有効になります．

```html
<a-assets>
  <a-mixin id="red" material="color: red"></a-mixin>
  <a-mixin id="blue" material="color: blue"></a-mixin>
  <a-mixin id="cube" geometry="primitive: box"></a-mixin>
</a-assets>

<a-scene>
  <a-entity mixin="red blue cube"></a-entity>
</a-scene>
```

## アセット <a-assets>

`<a-assets>`を使うことで，画像や動画やmixinを，ブラウザキャッシュに入れることができます．

※ 今のところ，Loader(`<a-model>`)で指定できるモデルファイルは<a-assets>には適用できないようです．


-------------------------
# 備わっている機能まとめ

### カメラ
* `<a-camera>` カメラ 
    * `position` 位置
        * デフォルト位置: `"0 1.8 4"`
    * `fov`視野角
        * デフォルトFOV: '80'°
    * `near` : ビューの前方クリップ (0.5)
        * ※ 0.5は少し遠すぎる気がしています．
    * `far` : ビューの後方クリップ (10000)

複数のカメラの設置や切替えは現在サポートされていません．

#### WASDコントロール
https://aframe.io/docs/components/wasd-controls.html

カメラは，WASDキーによる移動が可能となっています．
WASD移動の有効無効は，カメラで制御できます．

```html
<a-camera wasd-controls-enabled="false"></a-camera>
```


## 形状
https://aframe.io/docs/primitives/

#### ジオメトリ（定義済みプリミティブ）
* `<a-cube>` 直方体
    * 立方体ではないです
* 円柱 `<a-cylinder>`
* 球 `<a-sphere>`
* 画像 `<a-image>`
* モデル `<a-model>`
* 平面 `<a-plane>`
* `<a-curvedimage>` 曲がった画像
    * ユーザを囲むような曲面
* 全天球 `<a-sky>`
    * [正距円筒図法（Equirectangular-Projection）](https://en.wikipedia.org/wiki/Equirectangular_projection)の大きなテクスチャで全天球画像を表示
        * [flickrで見つけられる商用利用可能なCCライセンスのEquirectangular画像](https://www.flickr.com/search/?group_id=44671723%40N00&view_all=1&license=4%2C5%2C6%2C9%2C10)
* 動画 `<a-video>`
* 動画スフィア（360°動画） `<a-videosphere>`

## 属性

### 単位
単位はメートル単位の右手系です．Y軸が上方向を示します．
また，角度は，degree(0～360)指定を使います．
時間環形は，ミリ秒単位です（1000を指定すると1秒）．

#### ジオメトリ系
https://aframe.io/docs/components/geometry.html

※ジオメトリタイプによって，指定可能な属性は変わります．

##### 共通
* `visible` : 可視・不可視
* `translate` : ピボット位置からの相対的な位置
    * "0 0 0"のようにxyz座標を指定．
* `rotation` : 回転（向き）
    * "0 45 0"のようにxyzの回転角度(degree)を指定．

##### box ボックス
* `width` : 幅
* `height` : 高さ
* `depth` : 奥行き

##### circle，sphere
* `radius` : 円とか球の半径
* `segment` : 円とか球の近似に用いるセグメント数（n角形）
* `thetaStart` : 開始角（円弧のような部分円を描画するため
* `thetaLength` : 開始角から終了角までの長さ


#### 材質（マテリアル）系
* 色 `color`
* 金属度 `metalness`
* ラフネス `roughness`
* 透明ON/OFF `transparent`
    * true or false
* 透明度 `opacity`
    * [0, 1]
* ソース `src`
    * 状況によって異なる．基本的にはテクスチャのURL指定だと思ってください．
    * 動画も指定できます．
        * CSSのようなセレクタでも指定可能（src="#my-texture"）

#### ライト
* `type` : ライト種類
    * 環境光 "ambient"
    * 直接光 "directional"
    * 半球 "hemisphere"
    * 点光源 "point"
    * スポットライト "spot"
* `color` : ライト色
* `intensity` : 強度
* `decay` : （点光源＆スポットライト）減衰．
* `distance` : （点光源＆スポットライト）光源の有効範囲（intensityが0になる距離）．ゼロを指定した場合は減衰なし．
* `exponent` : （スポットライトの）ターゲット方向からどの程度消えていくか


#### アニメーション

https://aframe.io/docs/core/animation.html

* `attribute` : アニメーション属性 
    * ここにはアニメーションで変化させたい親要素の属性名を指定する
        * 例えば回転させたい場合は `rotation` を指定．
* `from` : 開始値
* `to` : 終了値
* `fill` : （？ 調べ中）
    * 次の値から指定 : backwards, both, forwards, none
* `repeat` : アニメーションの繰返し回数
    * 数値指定，または無限`indefinite`
* 'easing' : 補間処理＋α （開始終了時のちょっとしたアニメーション）
    * 'linear' : 線形補間
    * 'ease', 'ease-in', 'ease-out', 'ease-in-out'
        * 末尾に'-cubic'などを付与することが更に調整可能：
            * 'cubic', 'quad', 'quart', 'quint', 'sine', 'expo', 'circ', 'elastic', 'back', 'bounce' : ease系補間の追加オプション
* `dur` : アニメーション時間(ミリ秒)
* `begin` : アニメーション開始までのディレイ（遅延）
* `direction` : from～toの動かし方
    * `normal` : from -> to // from -> to // from -> to ...
    * `reverse` : to -> from // ...
    * `alternate` : from -> to -> from -> to -> ...
    * `alternateReverse : to -> from -> to -> from -> ...


#### 視点カーソル Cursor
https://aframe.io/docs/components/cursor.html

* `fuse` : fuse-basedのON/OFF
* `maxDistance` : 有効距離
* `timeout` : タイムアウト(fuse:trueの場合）

fuse-basedは，カーソルを一定時間オブジェクトに乗せていることで着火するタイプのカーソル．

----------------
# TIPS
http://aframevr.tumblr.com/ から．

### 擬似アイソメ（等角投影）表示にする
http://aframevr.tumblr.com/post/135369429110/introducing-aframe
FOVを非常に小さな値にして，カメラ位置を遠くに設定する．
また，wasdキー移動と，lookを無効にしている．

```html
<a-camera id="camera" near="1000" far="4000"
  position="0 880 1290"
  fov="2.2"
  cursor-visible="false"
  rotation="-34 0 0"
  wasd-controls-enabled="false"
  look-controls-enabled="false"></a-camera>
```

### フォグを設定する
http://aframevr.tumblr.com/post/135374507740/spheres-and-fog

シーンタグにfogの指定をする．

```html
<a-scene fog="type: linear; color: #AAB; far: 30; near: 0">
```

### 擬似ソフトシャドウの実現
http://aframevr.tumblr.com/post/135374507740/spheres-and-fog

影画像`<a-image>`を入れ子にして，貼り付ける．画像をX軸周りに-90度回転させることで影っぽい位置にしている．
ポイントは球の位置調整．`translate`を用いて可視ジオメトリの相対位置（球の半径分だけ上げる）を変更している．オブジェクトの位置自体は変化していない（`"-3 0 0"`）．

```html
<a-sphere position="-3 0 0" translate="0 1.75 0" radius="1.75" color="#7BC8A4" roughness="0.2">
  <a-image src="../_images/radial-shadow-3.png" rotation="-90 0 0" scale="1.75 1.75 1.75"></a-image>
</a-sphere>
```


# 所感
タグベースでシーン記述できるので，WebGLやWebVRを触ったことのない人でも今までとっつきやすく，思った以上に簡単にシーンを構築できるので，WebVRを盛り上げる一躍を担う存在になると予想されます．
ただ，少し凝ったことをしようとすると，Three.jsの世界でjavascriptをガリガリ書かないといけない点には注意が必要です．

entity-componentパターンを採用していることから，再利用性にかなり重点が置かれていて，コンポーネントを提供する側とそれらを組合せてシーンを構築する側の分業開発を想定しているのかな，とも思います．

しかしながら，現状ではnon-documentedな機能が多数ある点やノウハウが定まっておらず，開発途上のため今後の変更も多く入りそうなイメージです．採用判断は慎重にしたほうが良さそうですが，個人プロジェクトでちょっと作ってみるにはちょうど良いのではないでしょうか．




# リンク
[https://aframe.io/](https://aframe.io/)
https://github.com/aframevr/aframe/
[ドキュメント](https://aframe.io/docs/guide/)
[aframeで作成されたサイトいろいろ](https://github.com/aframevr/awesome-aframe)
[私版サンプル 適宜更新](http://cslroot.github.io/a-frame-examples)