Babylon.js で Oculus Quest で気軽に入れる VRクラブを作る (1)

# はじめに

最近自分の VRクラブ的なの作ってみたいと思って [AFrame](https://aframe.io/) で頑張ってたのですが、
友達に伝えたら [Babylon.js](https://www.babylonjs.com/) をオススメされたので、
じゃあせっかくだからということで Babylon.js で頑張っていくことにしました:fist:

現在の進捗は ↓ です。[^1]
（動画アップしてから気づいたのですが音が入ってないですね:mute:）
[![ビデオが開けなかった場合に表示されるテキスト](http://img.youtube.com/vi/A0F7NOe014E/0.jpg)](https://www.youtube.com/watch?v=A0F7NOe014E)

Babylon.js について参照できる日本語の資料として [CrossRoad](https://www.crossroad-tech.com/entry/Babylonjs_development_method) さんのブログがめちゃくちゃ参考になります。
しかし日本語の記事自体が少ないため、Babylon.js でやりたいことを行うのに、
今は海外の開発フォーラムを漁ったり、ドキュメント見ながら勉強しています。

そこで Babylon.js について参照できる日本語の記事を少しでも増やすため、
僕も Babylon.js の勉強ログを Qiita に記事として残していくことにしました :pencil: 

また、
内容が VRクラブということで HMD で見られないとテンションが上がらないため、
開発の最中はブラウザでデバッグ検証作業について進めていきますが、
最後の動作検証は Oculus Quest で進めていきます。

ちなみに HMD が無くても動作検証は出来ます。
コントローラ周りの検証は出来ませんが。。:video_game:

# 動作環境

- ホスティングサーバ
    - Node.js v11.14.0
    - Express 4.17.1
- フロントエンド
    - Babylon.js 4.0.3

# Babylon.js とは

 <img width="1292" alt="スクリーンショット 2019-06-30 16.34.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/ca828c14-54e6-acc7-aff9-1c5c838261f6.png">

Microsoft が公開したオープンソースの 3D ゲーム制作用フレームワークです。

WebVR についてもサポートされているため、
簡単に HMD 内ブラウザで楽しめるウェブコンテンツ制作が可能です。

実際に Babylon.js で制作されたウェブコンテンツは ↓ から体験することが可能です。
（HMD 対応コンテンツについては結構探したけど発見することが出来ず断念。。:sob:）
https://www.babylonjs.com/community/

開発のための [ドキュメント](https://doc.babylonjs.com/) が非常に充実しており、
手軽に Babylon.js のプログラムを試せる [Playground](https://playground.babylonjs.com/) も用意されており、
開発していく中で疑問に感じた点を実際に動かして潰せるのは心強かったりします。

GLSL も普通に動作するので [glslfan.com](https://glslfan.com/) 等で書いたものを、そのまま Babylon.js に持ってくることも可能です。
(Quest 用のチューニングとか細かい部分は少し変更する必要がありますが。。)

まためちゃビックリしたのですが [Unity で開発した内容を babylon.js に取り込む](https://www.crossroad-tech.com/entry/Babylonjs_Unity1) ことも可能なようで、こちらは実際にトライしてみたいと考えています。(まだ実際に試してはいない。。:skull_crossbones:)

# Babylon.js の検証環境のセットアップ

説明はこれくらいにして早速開発を進めていきたいところですが、
まずはスムーズに開発進めていくための開発環境を整えていきます。

(最初は [Playground](https://playground.babylonjs.com/) を使用して進めてもいいなと思ったのですが、
最終的に別の JS ライブラリ使ったり、コード行数が増えそうでファイル分割する予定なので)

## 1. Babylon.js の開発環境を構築する

開発環境と言っても JS 等 Web 関連のファイルを
ホスティングする環境を用意するだけになります。

今回はシンプルに [Express](https://github.com/expressjs/express) をホスティングサーバとして使用して、
 Babylon.js の開発環境をサクッと用意していきます :knife: 

まずは ↓ のコマンドで Express の環境をセットアップしていきます。

```shell
mkdir babylon-js
cd babylon-js
npm install --save express express-generator
npx express
npm install
npm run start 
```

`npm run start` 実行後、ブラウザで http://localhost:3000/ にアクセスした際に Express のページが表示されればホスティングサーバの準備は完了です。

また、よりサクッと検証進められるように `app.js` 内の ↓ をコメントアウトしておきます。

```javascript:babylon-js
// ルーティングしてもらう必要は無いためコメントアウト
// app.use('/', indexRouter);
// app.use('/users', usersRouter);
```

その後 `babylon-js/public` フォルダに ↓ の `index.html` ファイルを入れておきます。
当面の開発は `index.html` 内で進めていきます。Babylon.js の script タグも予め用意しておきます。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <!-- Babylon.js を CDN 経由でインポートする -->
    <script src="https://cdn.babylonjs.com/Babylon.js"></script>
</head>
<body>
    <div>Welcome to Babylon Club!!</div>
    <script>
        // 当面はここの script タグ内部で開発を進めていく
    </script>
</body>
</html>
```

再度 `npm run start` 実行後、ブラウザで http://localhost:3000/ にアクセスしてみます。
真っ白のページに `Welcome to Babylon Club!!` と表示されていれば問題ありません :thumbsup: 

現状のプロジェクトのフォルダ構成は ↓ のようになっていますが、
今後は基本的に `public` フォルダの中だけしかいじりません。

```
babylon-js/
├── app.js
├── bin
├── node_modules
├── package-lock.json
├── package.json
├── public <- このフォルダ内部しか触らない予定
│   ├── images
│   ├── index.html
│   ├── javascripts
│   └── stylesheets
├── routes
└── views
```

次に Babylon.js で動作検証するための、最低限必要なコードをプログラムに書いていきます :pencil: 

## 2. Babylon.js で簡易な 3D 空間を描画する

Babylon.js にはゲーム画面をレンダリングするために必要な **Scene** という概念が存在します。
基本的には Scene に様々なオブジェクトを配置したり、ライトを配置するなどしてゲーム画面を作っていく流れで開発を進めていくことになります。

またもう一つ大事な概念として **Camera** が存在します。
Camera を Scene に配置することで初めて Scene の中を見ることが出来るようになります。

それでは早速最低限 Scene を作って、
Camera でその中を見るためのコードを `index.html` に書いていきます。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <!-- Babylon.js 内でユーザの操作を受け取るために必要な style タグ -->
    <style>
        html, body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <!-- Babylon.js の描画内容をレンダリングするために使用する canvas タグ -->
    <canvas id="renderCanvas"></canvas>
    <script>
        // Babylon.js が描画する内容を WebGL などの低レイヤーな層に
        // 橋渡しするための Engine を描画対象の canvas エレメントで初期化する
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            // Engine で描画する Scene を作成する
            const scene = new BABYLON.Scene(engine);
        
            // Camera を 'MainCamera' という名前で作成する
            // 初期は (0, 0, -10) に Camera を Scene に配置する
            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, -10), scene);
        
            // Camera が向いている方向を原点に設定する
            camera.setTarget(BABYLON.Vector3.Zero());
        
            // Camera の操作を canvas 上で可能にする
            camera.attachControl(canvas, true);

            // 描画する準備の整った Scene を返却する
            return scene;
         }

         // createScene 関数を使用してシーンを作成する
         const scene = createScene();

         // Scene をレンダリングループの中で描画する
         engine.runRenderLoop(() => {
             scene.render();
         });

         // ブラウザのリサイズ処理をした際に適切にゲーム画面もリサイズされるようにする
         window.addEventListener('resize', function () {
            engine.resize();
         });
    </script>
</body>

</html>
```

↑ で `index.html` を上書きして `http://localhost:3000` にアクセスすると、画面全体が紫色っぽいページが表示されると思います。

これだけだと正常に Scene が作られて、
Camera で中の世界を見ることが出来ているのか全く分かりませんね:thinking:

Babylon.js には **MeshBuilder** というボックスや球といった、
簡易な 3Dの図形を作成してくれる仕組みがデフォで備わっています。

試しに先程の Scene に MeshBuilder を使用して球を配置してみたいと思います。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html, body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);
        
            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, -10), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            // 球を 'Sphere' という名前で Scene に追加する
            // 直径のサイズは 2m で設定する
            const sphere = BABYLON.MeshBuilder.CreateSphere('Sphere', { diameter: 2 }, scene);

            // 球の初期位置を原点に設定する
            sphere.position = BABYLON.Vector3.Zero();

            return scene;
         }

         const scene = createScene();
         engine.runRenderLoop(() => {
             scene.render();
         });

         window.addEventListener('resize', function () {
            engine.resize();
         });
    </script>
</body>

</html>
```

↑ で `index.html` を上書きして `http://localhost:3000` にアクセスすると、
今度は画面全体が紫色っぽいページの中心に球のシルエットが表示されると思います。

このままだと球が真っ黒で 3D っぽく見えませんね :thinking: 

こちら何故真っ黒の状態になってしまったかというと、
この Scene には今光源が何も設定されていないからなんですね。

そのため更にこの Scene に光源を追加します。
光源追加後の index.html は ↓ になります。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);

            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, -10), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            const sphere = BABYLON.MeshBuilder.CreateSphere('Sphere', { diameter: 2 }, scene);
            sphere.position = BABYLON.Vector3.Zero();

            // Scene に光源を追加する。今回は自然な環境光を再現するのに最適な HemisphericLight を使用する
            // 光は上空から照らしたいので (0, 1, 0) で上空から光を照らすように設定している
            // (0, -1, 0) と設定すれば下から光が照らされるようになる
            const light = new BABYLON.HemisphericLight('AmbientLight', new BABYLON.Vector3(0, 1, 0), scene);

            return scene;
        }

        const scene = createScene();
        engine.runRenderLoop(() => {
            scene.render();
        });

        window.addEventListener('resize', function () {
            engine.resize();
        });
    </script>
</body>

</html>
```

↑ で `index.html` を上書きして `http://localhost:3000` にアクセスすると ↓ のように球が見えるはずです :white_circle:

<img width="777" alt="スクリーンショット 2019-07-01 1.47.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/26ea8caa-7d6f-91df-0ee8-89c2eadfa857.png">

試しにマウスのドラッグで回転させたり、矢印キーを押して周囲から球を見てみましょう。
立体感のある感じで球が見えますね。これで開発/検証作業を進める準備は整いました :airplane:

次は VR クラブ感のある部屋を Scene 内に構築していきたいと思います。

# VR クラブの土台となる部屋を作る

先程まで説明した内容で VR クラブの土台は作成可能です。
それでは実際にどのように作成していくかについて説明していきます。

## 1. VR クラブの部屋の土台を作成する

今回は VRクラブの形を立方体に設定します。
現在表示している球を立方体に変更して、
立方体内部にカメラを配置するようにコードを少し変更します。

また MeshBuilder で作った立方体はデフォで外側にメッシュが貼られているため
`sideOrientation: BABYLON.Mesh.BACKSIDE` という記述を加えて、
立方体の作成時に内側にメッシュが貼られるようにします。
(じゃないと内側から見た時にまた紫色の空間が広がる状況になってしまう)

ライトの位置も立方体の内側から光を照らすために一旦原点に設置しちゃいます。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;

            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);

            // Camera の初期位置を原点に設定する
            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, 0), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            // 球を表示していた箇所を立方体に変更する
            // 'Box' という名前で辺の長さが 20メートルの正方形を作成して原点に配置する
            // sideOrientation: BABYLON.Mesh.BACKSIDE というオプションを追加してメッシュを反転させる
            const box = BABYLON.MeshBuilder.CreateBox('Box', { size: 20, sideOrientation: BABYLON.Mesh.BACKSIDE }, scene);
            box.position = BABYLON.Vector3.Zero();

            // ライトの位置を原点に設定する
            const light = new BABYLON.HemisphericLight('AmbientLight', new BABYLON.Vector3(0, 0, 0), scene);

            return scene;
        }

        const scene = createScene();
        engine.runRenderLoop(() => {
            scene.render();
        });

        window.addEventListener('resize', function () {
            engine.resize();
        });
    </script>
</body>

</html>
```

すると神々しい光が目の前に現れると思います:sunny:↓

<img width="1299" alt="スクリーンショット 2019-07-02 14.04.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/c628c923-8619-a6eb-bff7-94b9d8187d10.png">

周りを見渡しても同様の光景が広がっていると思います。
これは立方体の各面が光を正面同様に反射しているからです。

これで VR クラブの土台は完成しました :thumbsup: 

## 2. シェーダーで立方体の中をオシャレな空間にする

今のままだと全くクラブ感無いので GLSL というシェーダー言語を使用して、
少しクラブっぽい感じを味わえる空間にしたいと思います:space_invader:

まずマテリアルを先程の立方体に設定します。
マテリアルとは立方体等の図形 (Mesh) にどのような内容を描画するかを定義したものです。

マテリアルの説明については、[いんでぃーづ](https://indie-du.com/) さんの [Unity : マテリアル、メッシュ、シェーダの関係まとめ](https://indie-du.com/entry/2016/12/24/000541) という良記事が存在しているので、よく分からないという方はそちらをご参照ください。

今回は ShaderMaterial というものを使用して、
マテリアルを立方体に設定するのですが、その前に、
マテリアルの描画内容を GLSL で記述していきます :pencil: 

新たに `shaders` フォルダを作成します。
`shaders` フォルダ内に GLSL のファイルは追加していきます。

```
babylon-js/
├── app.js
├── bin
├── node_modules
├── package-lock.json
├── package.json
├── public
│   ├── images
│   ├── index.html
│   ├── javascripts
│   ├── stylesheets
│   └── shaders <- shaders フォルダを public フォルダの中に追加する
├── routes
└── views
```

今回使用するシェーダーは ↓ のようになりました。
サイケデリックな虹がタイル状に表示されるやつです。

```glsl:babylon-js/public/shaders/floor.vertex.fx
#ifdef GL_ES
    precision mediump float;
#endif

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Normal
varying vec2 vUV;

void main(void) {
    gl_Position = worldViewProjection * vec4(position, 1.0);
    vUV = uv;
}
```

```glsl:babylon-js/public/shaders/floor.fragment.fx
#ifdef GL_ES
    precision mediump float;
#endif

varying vec2 vUV;
uniform float time;

// https://qiita.com/kitasenjudesign/items/c8ba019f26d644db34a8
vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

void main() {
    vec2 st = vUV;
    vec3 color = vec3(0.0);

    st *= 5.0 + cos(time * 0.25) * 2.0;
    st = fract(st) + 1.0 + time * 0.75;

    float l = length(st);
    color = hsv2rgb(vec3(l * 0.15) * 5.0);

    gl_FragColor = vec4(color,1.0);
}

```

↑ の `floor.vertex.fx` と `floor.fragment.fx` を `shaders` フォルダに追加します。

現状のフォルダ構成はこのようになっています↓

```shell:babylon-js/public
.
├── images
├── index.html
├── javascripts
├── shaders <- public/shaders フォルダに vertex/fragment シェーダーを追加
│   ├── floor.fragment.fx
│   └── floor.vertex.fx
└── stylesheets
    └── style.css
```

これでようやくシェーダーで VRクラブ内の壁を描画する準備が整いました:v:

立方体にマテリアルを設定してシェーダーの描画内容を反映させた index.html は ↓ です。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;

            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);

            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, 0), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            const box = BABYLON.MeshBuilder.CreateBox('Box', { size: 20, sideOrientation: BABYLON.Mesh.BACKSIDE }, scene);
            box.position = BABYLON.Vector3.Zero();

            // shaders フォルダに追加した floor シェーダーを適用したマテリアルを作成する
            // vertex シェーダーは XXX.vertex.fx、fragment シェーダーは XXX.fragment.fx という命名規則で
            // 作成していると下記の記述でシェーダーを読み込むことが出来ます
            const shaderMaterial = new BABYLON.ShaderMaterial('floor', scene, './shaders/floor');

            // マテリアルを立方体に設定する
            box.material = shaderMaterial;

            const light = new BABYLON.HemisphericLight('AmbientLight', new BABYLON.Vector3(0, 0, 0), scene);

            return scene;
        }

        const scene = createScene();
        engine.runRenderLoop(() => {
            scene.render();
        });

        window.addEventListener('resize', function () {
            engine.resize();
        });
    </script>
</body>
```

↑で index.html 上書き後、`http://localhost:3000/` にアクセスするとサイケっぽい見え方になっているはずです:exclamation:  ↓

<img width="1193" alt="スクリーンショット 2019-07-03 21.04.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/57b25d45-3da9-5279-f286-a5cc4bad0578.png">

## 3. 時間経過と共にフロア内を動的なオシャレ空間にする

フロア内はそれっぽくなりましたが、
現在動きが無いのでイマイチクラブっぽさを感じることが出来ません :night_with_stars: 

そこで時間経過と共に立方体の模様が変化するように少しコードを変更します。

実は既に時間経過と共に模様が変化する仕組みは `babylon-js/public/shaders/floor.fragment.fx` に入っています。

↓ のコードでいうと `uniform time` が該当箇所になります。

```glsl:babylon-js/public/shaders/floor.fragment.fx
#ifdef GL_ES
    precision mediump float;
#endif

varying vec2 vUV;

// uniform で宣言しておくことで外部から time の値が設定可能になる。
uniform float time;

// https://qiita.com/kitasenjudesign/items/c8ba019f26d644db34a8
vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

void main() {
    vec2 st = vUV;
    vec3 color = vec3(0.0);

    st *= 5.0 + cos(time * 0.25) * 2.0;
    st = fract(st) + 1.0 + time * 0.75;

    float l = length(st);
    color = hsv2rgb(vec3(l * 0.15) * 5.0);

    gl_FragColor = vec4(color,1.0);
}

```

シェーダー内に `uniform` という記述を行うことによって、
外部から直接シェーダーに値を送ることが可能になっています。

今回は `uniform time` で宣言した変数に JavaScript から、経過時間を設定するように改修します。
Babylon.js でマテリアルのシェーダーに値を設定する記述自体は 1行です。
(time を設定する部分は 1行ではないです。。)

`index.html` は ↓ のようになります。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;

            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);

            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, 0), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            const box = BABYLON.MeshBuilder.CreateBox('Box', { size: 20, sideOrientation: BABYLON.Mesh.BACKSIDE }, scene);
            box.position = BABYLON.Vector3.Zero();

            const shaderMaterial = new BABYLON.ShaderMaterial('floor', scene, './shaders/floor');
            box.material = shaderMaterial;

            const light = new BABYLON.HemisphericLight('AmbientLight', new BABYLON.Vector3(0, 0, 0), scene);

            return scene;
        }

        const scene = createScene();

        // getMeshByName 関数で指定した名前が設定された Mesh を取得する
        // 今回はマテリアルが設定されている 'Box' という名前の Mesh を取得
        const box = scene.getMeshByName('Box')

        var time = 0.0;
        engine.runRenderLoop(() => {
            // 'Box' に設定されているシェーダーの time 変数に経過時間を設定する
            box.material.setFloat('time', time);
            scene.render();

            time += 0.01;
        });

        window.addEventListener('resize', function () {
            engine.resize();
        });
    </script>
</body>

</html>
```

これで静止していたサイケな空間が動き出したかと思います :rainbow: ↓

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/692389be-1d22-ccde-dafb-b6868d497c96.gif)

# Quest で動作確認する

基本的に Babylon.js では WebVR 対応にさせるための仕組みはデフォで備わっているのですが、
Quest のコントローラで移動したりする部分は自前で用意しないといけません。

まずは難しいことは考えず WebVR 対応させていきます。

## 1. Babylon.js で作成したコンテンツを WebVR 対応させる

`index.html` に2行追加するだけで可能です↓

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;

            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);

            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, 0), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            const box = BABYLON.MeshBuilder.CreateBox('Box', { size: 20, sideOrientation: BABYLON.Mesh.BACKSIDE }, scene);
            box.position = BABYLON.Vector3.Zero();

            const shaderMaterial = new BABYLON.ShaderMaterial('floor', scene, './shaders/floor');
            box.material = shaderMaterial;

            const light = new BABYLON.HemisphericLight('AmbientLight', new BABYLON.Vector3(0, 0, 0), scene);

            return scene;
        }

        const scene = createScene();

        // 下記の 2行で WebVR モードを選択することが出来るようになる
        const vrHelper = scene.createDefaultVRExperience();
        vrHelper.enableInteractions();

        const box = scene.getMeshByName('Box')

        var time = 0.0;
        engine.runRenderLoop(() => {
            box.material.setFloat('time', time);
            scene.render();

            time += 0.01;
        });

        window.addEventListener('resize', function () {
            engine.resize();
        });
    </script>
</body>

</html>
```

↑ で `index.html` を上書きすると画面右下にメガネのようなマークが表示されるようなります:dark_sunglasses:

<img width="284" alt="スクリーンショット 2019-07-03 21.26.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/659ebbba-841e-9888-a387-a4739e8586f7.png">

右下の眼鏡のようなマークが確認出来ていれば Quest で Babylon.js コンテンツの中を見るための準備は完了です。

また、今回は Quest から PC にアクセスするため、プロジェクトフォルダのルートにある `bin/www` ファイル内の、
`server.listen(port);` を　`server.listen(port, '0.0.0.0');` に変更しておきます。

## 2. Quest で Babylon.js のコンテンツを見る

`npm run start` で動かしていた Node.js サーバーを再起動します。

予め Node.js を動作させているサーバーの IP アドレスを控えておきます。
その後 Quest のブラウザを開き `http://<サーバーの IP アドレス>:3000` にアクセスします。

![quest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/ab40ee8b-485a-925f-8069-903abbc65dc0.gif)

Quest で VR クラブ内に入れました :exclamation:  :thumbsup: 

## 3. Quest で Babylon.js のコンテンツ内を歩き回れるようにする

Babylon.js ではコントローラの制御も簡単な記述で行うことが可能です。
今回はソードオブガルガンチュア的な移動が出来るようにします。

左コントローラのスティックで移動できるようにして、
右コントローラのスティックで Y軸回転出来るようにします。

`index.html` は ↓ の感じになります。

```html:babylon-js/public/index.html
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    <title>Babylon Club</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        html,
        body {
            overflow: hidden;
            width: 100%;

            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);

        const createScene = () => {
            const scene = new BABYLON.Scene(engine);

            const camera = new BABYLON.FreeCamera('MainCamera', new BABYLON.Vector3(0, 0, 0), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas, true);

            const box = BABYLON.MeshBuilder.CreateBox('Box', { size: 20, sideOrientation: BABYLON.Mesh.BACKSIDE }, scene);
            box.position = BABYLON.Vector3.Zero();

            const shaderMaterial = new BABYLON.ShaderMaterial('floor', scene, './shaders/floor');
            box.material = shaderMaterial;

            const light = new BABYLON.HemisphericLight('AmbientLight', new BABYLON.Vector3(0, 0, 0), scene);

            return scene;
        }

        const scene = createScene();

        const vrHelper = scene.createDefaultVRExperience();
        vrHelper.enableInteractions();

        // 現在の左右への傾き回数を保持する
        // 1だったら 45度, 2 だったら 90度, -1 だったら -45度にY軸回転する
        var _rotationAngle = 0;

        // VR 空間にコントローラを表示するための記述
        vrHelper.onControllerMeshLoaded.add((webVRController) => {
            var isHorizontalRotate = false;

            // onPadValuesChangedObservable を使用することで、
            // コントローラのスティック入力情報が取得できるようになる
            webVRController.onPadValuesChangedObservable.add((stateObject) => {
                // 左側のコントローラのスティック操作
                if (webVRController.hand === 'left') {
                    // 右スティックで回転した向きを正面として、
                    // スティックを左右に傾けたら左右に移動し、
                    // スティックを上下に傾けたら前後に移動する
                    const matrix = new BABYLON.Matrix();
                    const deviceRotationQuaternion = vrHelper.currentVRCamera.rotationQuaternion;
                    BABYLON.Matrix.FromQuaternionToRef(deviceRotationQuaternion, matrix);

                    const move = new BABYLON.Vector3(stateObject.x * moveSpeed, 0, -stateObject.y * moveSpeed);
                    const addPos = BABYLON.Vector3.TransformCoordinates(move, matrix);
                    vrHelper.position = vrHelper.position.add(addPos)

                    // 右側のコントローラのスティック操作
                } else {
                    // スティックを左か右に一定以上傾けたら
                    // スティックを向けている方向にY軸で 45 度回転させる
                    if (isHorizontalRotate && Math.abs(stateObject.x) > 0.8) {
                        isHorizontalRotate = false;

                        // 傾きに応じて _rotationAngle を増減させる
                        if (stateObject.x > 0) {
                            _rotationAngle++;
                        } else {
                            _rotationAngle--;
                        }

                        var target = BABYLON.Quaternion.FromRotationMatrix(BABYLON.Matrix.RotationY(Math.PI / 4 * _rotationAngle));
                        vrHelper.currentVRCamera.rotationQuaternion = target
                    } else if (Math.abs(stateObject.x) < 0.8) {
                        isHorizontalRotate = true
                    }
                }
            });
        });

        const box = scene.getMeshByName('Box')

        var time = 0.0;
        engine.runRenderLoop(() => {
            box.material.setFloat('time', time);
            scene.render();

            time += 0.01;
        });

        window.addEventListener('resize', function () {
            engine.resize();
        });
    </script>
</body>

</html>
```

この状態で再度 Quest のブラウザからアクセスしてみます。

![quest_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/8b3842a7-b72e-61b1-c7e8-933d7ba8c015.gif)

部屋に入れてコントローラーで移動も回転も出来たーーー！！！！ :tada: :clap: :cake: 

# おわりに

次回は実際に音楽を流し、音量に応じて周りにエフェクトを加えていくというのを [Tone.js](https://tonejs.github.io/) を交えて実装していきます :thumbsup: 

この記事を見て少しでも Babylon.js にご興味を抱かれた方がいれば嬉しいです :relaxed:

最終的には WebSocket とか WebRTC 連携させてソーシャル化まで行けたら良いなと考えております :fire: 

[2019/07/14] [Part2](https://qiita.com/nuhs/items/e36a5a8fe31cdd9d7043) を書きました :exclamation:  :writing_hand:

# 参考リンク

- [3Dゲーム用JavaScriptフレームワークの「Babylon.js 3.0」が公開](https://mag.osdn.jp/17/07/24/150000)
- [Babylon.js向けデータをUnityから出力する](https://www.crossroad-tech.com/entry/Babylonjs_Unity1)
- [babylon.js で Material を自作する方法](https://qiita.com/il-m-yamagishi/items/364bd8dc1e56ea79eaad)
- [【2019/3/5更新】Babylon.jsを使った開発方法が色々あるので整理しました](https://www.crossroad-tech.com/entry/Babylonjs_development_method)
- [glslでhsv2rgbとrgb2hsv](https://qiita.com/kitasenjudesign/items/c8ba019f26d644db34a8)

[^1]: 適当なソースコードを簡単な正規表現でパースしながら、それを譜面に起こして Tone.js で鳴らしながら、音の大きさをリアルタイムに Babylon.js 内の空間でビジュアライズしてる感じです。