WebVRを触ってみる

この記事は[Recruit Engineers Advent Calendar](http://www.adventar.org/calendars/1617)の9日目の記事です。

また、この記事は2016年に書かれた古い記事です。当時は今とは状況がかなり異なっています。参考にする場合注意してください。

## はじめに
2016年ももうすぐ終わりです。
今年はVR元年とも呼ばれる年でした。秋にはPlayStationVRが発売されたりと各所で盛り上がりを見せているVRですが、webにもその波が押し寄せています。

Mozilla VRチームが中心となって開発を進めてきた[WebVR API](https://webvr.info/)を利用することで、PCとVRデバイスを接続すればブラウザ上でVR体験が可能になります。
OculusRiftやDaydreamなど、VRコンテンツはそれぞれのプラットフォームに閉じてしまっていますが、webのオープン性を持ち込むことでより多くのメディアの架け橋となり、VRが浸透していく上で一翼を担う存在になるのではないかと思います。

今回はWebVRとその実装アプローチ、またVRコンテンツ上のインタラクションといった部分にも触れてみたいと思います。

## ブラウザVRの仕組み
ブラウザVRの仕組みを簡単に説明すると、WebVRに対応したブラウザを開いてヘッドマウントディスプレイ(以降HMD)を接続することで、ブラウザ上のコンテンツがHMDに表示され、顔の向きや頭の位置と連動してコンテンツが表示されるというものです。

![20161203154424.jpg](https://qiita-image-store.s3.amazonaws.com/0/73643/3a4b9173-6c65-be14-4b95-a18f9ed6a4ad.jpeg)
(引用: https://developer.mozilla.org/ja/docs/Web/API/WebVR_API)

ただ、HMDは高価なので手軽にVR体験するなら[Google Cardbord](https://vr.google.com/cardboard/)などを利用してスマートフォンで見てみるのがいいでしょう。[^注1]

*(追記: 2019.2.16)* 
iOS Safari 12.2からセキュリティ対策の名目でdeviceorientationがデフォルトで利用できなくなります。
https://github.com/w3c/deviceorientation/issues/57#issuecomment-458464696

## WebVR API

#### 仕様
WebVR APIはW3CにおいてEditor’s Draftとなっており、現在仕様策定中です。
https://w3c.github.io/webvr/

#### 対応ブラウザ
ブラウザの対応状況(2016/12現在)ですが、[こちら](https://iswebvrready.org/#webvr-api-(navigator.getvrdisplays))をみると

- Chrome experimental builds 
(https://webvr.info/get-chrome/)
- Firefox Nightly Builds
- Samsung Internet Browser for GearVR

のみとなっており、まだまだこれからという感じがします。
※ 今年の9月にはEdgeもWebVRを[サポートする方針を表明](https://blogs.windows.com/msedgedev/2016/09/09/webvr-in-development-edge/#0zcfHdRGaXSKlE0x.97)しています。

#### インターフェース
WebVR APIを利用するとざっくり以下のようなことができます。

- VRデバイス情報の取得 ([HMDVRDevice](https://developer.mozilla.org/ja/docs/Web/API/HMDVRDevice))
- VRデバイスの位置情報取得 ([PositionSensorVRDevice](https://developer.mozilla.org/ja/docs/Web/API/PositionSensorVRDevice))
- VRデバイスのイベント情報取得 ([onvrdisplayconnected](https://developer.mozilla.org/ja/docs/Web/API/Window/onvrdisplayconnected), [onvrdisplaydisconnected](https://developer.mozilla.org/ja/docs/Web/API/Window/onvrdisplaydisconnected), [onvrdisplaypresentchange](https://developer.mozilla.org/ja/docs/Web/API/Window/onvrdisplaypresentchange))
- 描画補助情報取得 ([VREyeParameters](https://developer.mozilla.org/en-US/docs/Web/API/VREyeParameters))

見ての通り、主にVRデバイスの情報を取得するだけのAPIです。
この情報を利用してVR体験を作っていくのはコンテンツ実装者の仕事になります。

## 実装アプローチ
では具体的な実装方法を見ていきます。以下、大まかな流れです。
1. Javascriptを用いてCanvas要素にWebGL描画し、3Dコンテンツを作成
2. WebVR APIを使ってVRデバイスの位置情報を取得
3. 取得した位置情報を3Dコンテンツ内のカメラの位置情報に適応
4. 左目・右目用画面の描画処理 (VR用に画面を2分割)
５. requestAnimationFrameで2~4を繰り返す

ブラウザ上の3D空間はWebGLで作っていきますが、まともに実装すると結構な学習コストがかかってしまうので、[Three.js](https://threejs.org/)などの3Dグラフィック描画ライブラリを利用します。
Three.jsのリポジトリにはVRControls,VREffectという便利なVR用モジュールがあり、２と4の処理を勝手に行ってくれます。

**VRControls.js**
VRデバイスの位置情報と3D空間のカメラ位置を連動させる 
https://github.com/mrdoob/three.js/blob/dev/examples/js/controls/VRControls.js

**VREffect.js**
画面を2分割し、立体視のために左目・右目用描画処理の差異計算を行う
https://github.com/mrdoob/three.js/blob/dev/examples/js/effects/VREffect.js

### 代表的なライブラリ/フレームワーク
WebVRコンテンツを作るためのライブラリやフレームワークも数多くあるようなので、代表的なものをいくつかまとめてみました。

||特徴|URL|
|:-----------|:------------|:------------|
|WebVR Boilerplate|Three.jsで実装した3DコンテンツをVR対応させる|[https://github.com/borismus/webvr-boilerplate](https://github.com/borismus/webvr-boilerplate)|
|A-Frame|HTMLタグ形式でVRコンテンツを作成する|[https://aframe.io/](https://aframe.io/)|
|Solufa|Virtual DOMを利用してVRコンテンツを作成する。A-Frameよりも高速といわれている|[http://solufa.io/](http://solufa.io/)|
|Vizor|ブラウザ上でVR空間、360°コンテンツを作成し、公開可能|[http://vizor.io/](http://vizor.io/)|
|InstaVR|ブラウザ上でナビゲーション、インフォメーションをつけた360°コンテンツが作成可能|[http://jp.instavr.co/](http://jp.instavr.co/)|

今回はこの中からWebVR Boilerplate, A-Frameを利用したコンテンツを作ってみたいと思います。

## WebVR Boilerplate
WebVR BoilerplateはThree.jsで作った3DコンテンツをVR対応させるためのライブラリです。
実態はVRManagerというモジュールで、コンテンツの全画面対応や画面の2分割対応を右下のアイコンから行うことができます。

最初にThree.jsを使って簡単な3Dコンテンツのサンプルを作ってみました。

[これ](https://shiruco.github.io/WebVR_Sample/three/)です
![three-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/73643/73de3f6b-ae26-1bd6-1f65-e4d3ab838ace.gif)
([source](https://github.com/shiruco/WebVR_Sample/blob/master/three/src/app.js))


しかしこれだけだとただの3Dコンテンツなので、WebVR Boilerplateを使ってVR対応し、宇宙の中に飛び込んでいきたいと思います。

まずはwebvr-boilerplateをnpmインストールします。

```bash
npm install webvr-boilerplate
```
WebVRを使うためのpolyfillもインストールします。

```bash
npm install webvr-polyfill
```

さらにVRControls.js,VREffect.jsも落としてきて読み込みます。

```js
import 'webvr-polyfill';
import WebVRManager from 'webvr-boilerplate';
import VRControls from './modules/VRControls';
import VREffect from './modules/VREffect';
```

これでVR対応させるための準備は整いました。
あとは読み込んだモジュールを使って実装していくのですが、ポイントだけ掻い摘んで書くと、

はじめにモジュールをそれぞれ初期化し、

```js
// VRControlsの初期化
// 位置情報更新のためにcameraを渡す
const controls = new VRControls(camera);

// VREffectの初期化
const effect = new VREffect( renderer );
// 画面分割のために画面のサイズを渡す
effect.setSize(window.innerWidth, window.innerHeight);

// VRManagerの初期化
const manager = new WebVRManager(renderer, effect);
```

requestAnimationFrameのタイミングで位置情報の再計算、画面の再描画を行います。これだけです、簡単ですね。

```js
// VRControlsの位置情報更新
controls.update()

// VRManagerでの再描画
manager.render( scene, camera )
```

上記の実装を入れて出来上がったものが[こちら](https://shiruco.github.io/WebVR_Sample/three_demo/)です。[^注2]
![three_vr-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/73643/caaac44f-ed88-859c-887f-f224e884ed10.gif)
([source](https://github.com/shiruco/WebVR_Sample/blob/master/three_vr/src/app.js))

キャプチャでは伝わりづらいのですが、Google Cardbordなどを使うと頭の位置に合わせてカメラが動き、あたかもそこにいるような体験ができます。お手元にあれば是非見てみてください。[^注3]

## A-Frame

A-Frameを利用すればHTML形式でVRコンテンツを作成することができます。
また、WebVRにデフォルトで対応しているためVR対応を意識することなく開発できます。
詳しい仕様は[公式ドキュメント](https://aframe.io/docs/master/introduction/)に書いてあるので参考にしてください。

こちらも同様にサンプルを作ってみました。

[これ](https://shiruco.github.io/WebVR_Sample/aframe/)です
![aframe-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/73643/e25820c6-29f5-cbea-9bc9-2264a1dda139.gif)
([source](https://github.com/shiruco/WebVR_Sample/blob/master/aframe/index.html))

Javascriptを一行も書くことなく、印象派画家の美術館的な何かが出来上がりました。[^注4]
やっていることは、全ての要素の親となる`<a-scene>`に対して`<a-entity>`、`<a-plane>`といった各種ノードをオブジェクト単位で追加していき、位置情報やテクスチャはその属性値として定義しているだけです。

このコンテンツにインタラクション要素を加えてみます。

#### インタラクション
VRコンテンツでのインタラクションを考える場合、操作面では

- 視点カーソル
- コントローラ (Gamepad etc) 

などが候補に挙がります。これらを使ってユーザーアクションを受け付けるわけですが、WebVRの場合HMDに加えコントローラまで用意できる人はごく少数だと思われるため、個人的には視点カーソルが主流になっていくのではないかと思います。

**cursorコンポーネント**

視点カーソルはA-Frameの`cursor`コンポーネントで実装します。
cursorは`fuse`というプロパティを持っています。これは視点カーソル上のオブジェクトに対してclickイベントを発行するかどうかのフラグです。しかしこれをtrueにするだけだと、視点と交差する全てのオブジェクトに都度clickイベントが発火してしまうため、`timeout`プロパティで指定した時間凝視することで発行するようにします。
以下は3秒に設定した例です。

```html
<a-entity camera look-controls>
    <a-entity cursor="fuse: true; timeout: 3000;" position="0 0 -2"
                    geometry="primitive: ring; radiusOuter: 0.05; radiusInner: 0; segmentsTheta: 45"
                    material="color: #A6AAF4">
    </a-entity>
</a-entity>
```

その他、cursorがオブジェクトに乗ったときに発火する`mouseenter`、離れたときに発火する`mouseleave`などがあり、これらを使ってインタラクティブなコンテンツを作っていきます。

で、出来上がったのが[こちら](https://shiruco.github.io/WebVR_Sample/aframe_interaction/)です。
![aframe_interaction-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/73643/305aa1f2-fca4-b154-1daa-f4438bbe8adc.gif)
([source](https://github.com/shiruco/WebVR_Sample/blob/master/aframe_interaction/index.html))

画面の中心に視点カーソル(薄紫の円)が表示されています。
絵画の上にカーソルが乗ると、説明用のパネルが表示され、
そのまま3秒間見つめ続けるとダミーのメニューが出てきます。
このサンプルではメニューを凝視しても何もおきませんが、メニューをクリック(凝視)することで次のコンテンツ、また次のコンテンツへとリンクできればより面白いものになりそうです。

## おわりに
今回はWebVRの仕様から実装まで広く浅くという感じで書いてみました。実際にコンテンツを作ってみた所感ですが、便利なライブラリを利用すれば意外と簡単にそれっぽいものが作れるなという印象です。特にA-Frameなどは、Javascriptを知らない人でもHTMLの知識さえあればVRコンテンツが作れてしまいます。
UnityやUnrealEngineで作られたグラフィックにはまだまだ敵いそうにありませんが、webの強みを生かしたコンテンツを作ることはできそうです。
Googleの「[VR Shell](http://vrinside.jp/news/chormevr/)」に続きFacebookのVR用ブラウザ「[Carmel](http://vrinside.jp/news/oculus-carmel/)」、WebVR開発のための「[React VR](https://techcrunch.com/2016/10/06/oculus-webvr/)」の発表など、来年もさらに盛り上がりそうなWebVR。
今後もその動向に注目していきたいと思います。

## 参考
- https://chromium.googlesource.com/experimental/chromium/src/+/refs/wip/bajones/webvr_1/third_party/WebKit/Source/modules/vr
- https://blog.mozvr.com/connecting-virtual-worlds-hyperlinks-in-webvr/
- https://developer.mozilla.org/ja/docs/Web/API/WebVR_API
- https://aframe.io/
- https://github.com/borismus/webvr-boilerplate
- http://cardboardclub.jp/lab/chromeex-02/

## 注釈
[^注1]: スマートフォンの場合はジャイロセンサーによって位置情報を取得するため、内部的にはWebVR APIは利用していません
[^注2]: HMD上での表示を再現したもので、ソースの結果とは異なります
[^注3]: サンプルは全て iphone6(iOS9.3.3) safariでのみ動作確認しています
[^注4]: コンテンツ中の絵画は全てパブリックドメインのものを使用しています (https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%89%E3%83%BB%E3%83%A2%E3%83%8D#/media/File:Claude_Monet_011.jpg https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%89%E3%82%AC%E3%83%BC%E3%83%BB%E3%83%89%E3%82%AC#/media/File:Edgar_Germain_Hilaire_Degas_018.jpg https://ja.wikipedia.org/wiki/%E3%83%94%E3%82%A8%E3%83%BC%E3%83%AB%EF%BC%9D%E3%82%AA%E3%83%BC%E3%82%AE%E3%83%A5%E3%82%B9%E3%83%88%E3%83%BB%E3%83%AB%E3%83%8E%E3%83%AF%E3%83%BC%E3%83%AB#/media/File:Mlle_Irene_Cahen_d%27Anvers.jpg https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%B3%E3%82%BB%E3%83%B3%E3%83%88%E3%83%BB%E3%83%95%E3%82%A1%E3%83%B3%E3%83%BB%E3%82%B4%E3%83%83%E3%83%9B#/media/File:VanGogh-starry_night_ballance1.jpg)