babylon.js の使い方（随時更新）

お仕事で babylon.js を使うことになったので、勉強したことをまとめます。


# 承前

babylon.js については [公式のチュートリアル](https://doc.babylonjs.com/babylon101/) が充実しているので、基本的にそれだけで事足ります。
チュートリアルを一通り読んで基礎事項を押さえれば、あとは必要に応じて [HOW TO](https://doc.babylonjs.com/how_to/) や [API](https://doc.babylonjs.com/api/) を調べるだけでよい状態になれます。

この記事では、公式のチュートリアルでは少しわかりづらいと感じた箇所について、補足事項を書いていきます。
なお、この記事は随時更新予定です。


# Material

## Color

3D の物体表面の色は、光に含まれる各色の強度と物体表面の各色の反射率の積で計算されます。

例えば、光の RGB の各色の強度が `(1,0,1)` で物体の反射率が `(1,0,0)` だとすると、それぞれの要素の値を掛け合わせた `(1,0,0)` が最終的に見える物体表面の色になります。同様に、反射率が `(0,1,0)` の物体なら、最終的な色は `(0,0,0)` になります。

Material では、この計算に使用される物体の各色の反射率を定義できます。
定義は以下のように、Material オブジェクトのプロパティへの設定を通して行います。

```javascript
var material = new BABYLON.StandardMaterial("material", scene);

material.diffuseColor  = new BABYLON.Color3(1, 0, 1);
material.specularColor = new BABYLON.Color3(0, 1, 0);
material.emissiveColor = new BABYLON.Color3(0, 0, 1);
material.ambientColor  = new BABYLON.Color3(1, 1, 1);

mesh.material = material;
```

上記のプロパティは、それぞれが特定の光に対する物体の色の反射率を表しています。
以下、それぞれのプロパティの詳細について説明します。


### diffuseColor

光源からの光に対する物体の反射率を設定します。

ここでいう光源とは `~Light` 系のコンストラクタを使用して生成されるオブジェクトを指します。`HemisphericLight` や `SpotLight` などが該当します。
各光源には `diffuse` プロパティを介して光に含まれる各色の強度を設定できます。

```javascript
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.diffuse = new BABYLON.Color3(1, 1, 1);
```

`diffuseColor` に白・赤・緑を設定した Material を作成し、上記のコードで定義した光源下でどのように見えるかを確認してみます。それぞれの Material の定義は以下の通りです。

```javascript
var white = new BABYLON.StandardMaterial("white", scene);
white.diffuseColor = new BABYLON.Color3(1, 1, 1);

var red = new BABYLON.StandardMaterial("red", scene);
red.diffuseColor = new BABYLON.Color3(1, 0, 0);

var green = new BABYLON.StandardMaterial("green", scene);
green.diffuseColor = new BABYLON.Color3(0, 1, 0);
```

結果は以下のようになります。
光源からの光が RGB の全ての成分を含んでいるので、箱がそれぞれ白・赤・緑になっているのが確認できます。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="RwaMbpX" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - diffuse sample">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/RwaMbpX">
  babylon.js - diffuse sample</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

ここで光源の `diffuse` から緑成分を取り去ってみます。
光源の定義は以下のようになります。

```javascript
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.diffuse = new BABYLON.Color3(1, 0, 1);
```

結果は以下のようになります。
光源からの光に緑成分が含まれていないので、白い箱は紫に、緑の箱は黒に変化します。一方で、赤い箱は赤いままになっているのがわかります。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="BaKrBOL" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - diffuse sample 2">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/BaKrBOL">
  babylon.js - diffuse sample 2</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


### specularColor

反射光の色を定義します。

反射光といっても、物体から反射した光が他の物体に照射されたりするわけではありません。
ここで反射光という言葉が指しているのは、物体が光に照らされた際に、物体表面が光って見えるエフェクトのことです。

言葉で説明してもわかりづらいので、例を示します。

以下の例では `specularColor` を `(1,1,1)` に設定しています。
視点を動かすと、箱の上の面が光を反射して光る（艶があるように見える）のがわかると思います。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="NWNYpmW" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - specular sample">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/NWNYpmW">
  babylon.js - specular sample</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


次の例では `specularColor` を `(0,0,0)` に設定しています。
視点を動かしても、箱の上の面は特に光りません。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="OJNvpYe" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - specular sample 2">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/OJNvpYe">
  babylon.js - specular sample 2</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

上の例で見たように、`specularColor` では物体表面が光って見えるエフェクトを制御します。
同様に `specularColor` を `(1,0,0)` に設定すれば、反射光を赤く見せることもできます。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="RwaMpXE" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - specular sample 3">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/RwaMpXE">
  babylon.js - specular sample 3</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

なお `diffuse` と同様、こちらも `specular` プロパティを通して、光源が放つ `specularColor` に作用する光の色を設定できます。

```javascript
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.specular = new BABYLON.Color3(1, 1, 1);
```


### emissiveColor

物体表面それ自体が光っている状態を表現できます。
光源がない環境下でも明るく見えます。

以下の例では `emissiveColor` に `(1,0,0)` を設定しています。
光源は生成していませんが、箱が真っ赤に表示されているのがわかると思います。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="OJNvmLK" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - emissive sample">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/OJNvmLK">
  babylon.js - emissive sample</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

なお、光源によって `emissiveColor` が設定された面が照らされた場合、光源の光の色（厳密には `diffuse` と `diffuseColor` によって生じる色）と `emissiveColor` の色が混色されます。
上の例に青色 `(0,0,1)` の光を放つ光源を追加した例を以下に示します。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="GRZxmpa" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - emissive sample 2">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/GRZxmpa">
  babylon.js - emissive sample 2</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


### ambientColor

環境光に対する物体の色の反射率を定義します。

babylon.js の scene には環境光という特別な光が設定されています。
環境光には、以下のように `ambientColor` プロパティを通して色の設定を行います。

```javascript
scene.ambientColor = new BABYLON.Color3(1, 1, 1);
```

Material の `ambientColor` の初期値は `(0,0,0)` のようです。
そのため `ambientColor` プロパティに明示的に値を設定しない限り、環境光は効果を発揮しません。

以下の例では環境光を `(1,1,1)` に設定していますが、Material には何も設定していません。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="eYZMWXK" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - ambient sample">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/eYZMWXK">
  babylon.js - ambient sample</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

一方、Material の `ambientColor` に `(1,0,0)` を設定した場合は以下のようになります。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="QWNmvPM" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - ambient sample 2">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/QWNmvPM">
  babylon.js - ambient sample 2</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


## ShaderMaterial

`ShaderMaterial` を利用することでオブジェクトに自作シェーダーを適用することができます。
シェーダーについては以下の babylon.js の公式ページに詳しく書いてあります。

- [Introduction to Shaders](https://doc.babylonjs.com/advanced_topics/shaders/introToShaders)
- [Putting Shader Code Into Babylon.js](https://doc.babylonjs.com/advanced_topics/shaders/shaderCodeInBjs)
- [Shader Material](https://doc.babylonjs.com/advanced_topics/shaders/shaderMaterial)

全部を説明すると大変な文量になってしまうため、ここではさわりだけ説明します。

`ShaderMaterial` 経由で自作シェーダーを使用するための必要最低限のコードは以下の通りです：

```js
Effect.ShadersStore["customVertexShader"] = `
  // type your vertex shader here
`;
Effect.ShadersStore["customFragmentShader"] = `
  // type your fragment shader here
`;
var shaderPath = {
  vertex: "custom",
  fragment: "custom",
};
var option = {
  attributes: ["position", "normal", "uv"],
  uniforms: ["world", "worldView", "worldViewProjection", "view", "projection"],
}
var shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, shaderPath, option);
```

大まかには `shaderPath` に `"custom"` を指定して `Effect.ShadersStore` にカスタムシェーダーのコードを記述するという構成です。シェーダーのコードは GLSL で記述します。

GLSL は Babylon.js の裏にある WebGL の世界でコンパイルされて動作します。この際、Babylon.js は GLSL のコードと一緒に必要なデータを WebGL の世界に引き渡します。

`option` で指定している `attributes` と `uniforms` は Babylon.js から WebGL の世界に引き渡す変数を定義しています。変数には Babylon.js のエンジンが勝手に用意してくれるものとプログラマが自分で宣言するものの２種類あるのですが、ここでは前者のみが対象です。後者については `ShaderMaterial` の `setFloat` や `setTexture` を使用して引き渡します。

文面だけではわかりづらいと思うので例を示します。以下はカスタムシェーダを使ってランダムな色のテクスチャを持つ Box を作成するサンプルです。

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="YzYWLPd" data-user="dmystk" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/YzYWLPd">
  babylon.js - shader material</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

以下は Vertex シェーダーを記述している箇所の抜粋です。Vertex シェーダーと Fragment シェーダーについては、先に掲載した [Introduction to Shaders](https://doc.babylonjs.com/advanced_topics/shaders/introToShaders) を参照ください。

```js
  BABYLON.Effect.ShadersStore["customVertexShader"] = `
    precision highp float;
    // Attributes
    attribute vec2 uv;
    attribute vec3 position;
    // Uniforms
    uniform mat4 worldViewProjection;
    // Varying
    varying vec2 vUV;
    void main(void) {
      gl_Position = worldViewProjection * vec4(position, 1.0);
      vUV = uv;
    }
  `;
```

`attribute` と `uniform` として宣言されているものが Babylon.js から渡される変数群です。
ここで宣言している `uv`・`position`・`worldViewProjection` は、いずれも先ほど `option` で指定されていたことに注意してください。

`varying` は Vertex シェーダーから Fragment シェーダーに値を渡す際に使用する変数です。これは以下の Fragment シェーダーのコードを見るとわかりやすいです。

```js
  BABYLON.Effect.ShadersStore["customFragmentShader"] = `
    precision highp float;
    // Uniforms
    uniform sampler2D uTextureSampler;
    // Varying
    varying vec2 vUV;
    void main(void) {
      gl_FragColor = texture2D(uTextureSampler, vUV);
    }
  `;
```

Vertex シェーダーの中で `varying` として宣言していた `vUV` と同名の変数が宣言されています。この `vUV` には Vertex シェーダーから渡された値が格納されます。

Fragment シェーダーの中でも `uniform` が宣言されていますが、ここで宣言されている `uTextureSampler` は先ほどの `option` の中には含まれていない変数（＝プログラマの自作変数）です。`uTextureSampler` の値は以下の箇所で設定されています。

```js
  let material = new BABYLON.ShaderMaterial("smat", this.scene, {vertex: "custom", fragment: "custom"}, {
    attributes: ["position", "normal", "uv"],
    uniforms: ["world", "worldView", "worldViewProjection", "view", "projection"],
  });

  // 中略

  material.setTexture("uTextureSampler", texture);
```

今回は GLSL の `sampler2D` 型の値を設定したいので `ShaderMaterial` の `setTexture` をコールして値を設定しています。


# Camera

## Multi Canvases

`Engine` の `registerView()` を使うことで、１つの Babylon.js インスタンスに対して、複数の canvas を割り当てることができます。
それぞれの canvas で異なるカメラを使用することも可能です。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="js,result" data-user="dmystk" data-slug-hash="jOMZxeo" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="babylon.js - multi canvases sample">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/jOMZxeo">
  babylon.js - multi canvases sample</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

> Safari だと表示が崩れるようです。
> Chrome では正常に表示されることを確認済みです。


## Orthographic Camera

通常、我々が babylon.js の世界でカメラを作成した場合、そのカメラは透視投影を使用したカメラです。透視投影では、１点を中心に放射状に光線を投影するため、遠くのものが小さく、近くのものが大きく画面に表示されます。

一方で、掲題の垂直投影カメラ（Orthographic Camera）は光線を平行に投影します。光線が平行に投影されるため、遠くのものも近くのものも一定の寸法で画面に表示されます。

例えば、カメラのごく近くに箱があり、その遥か向こう側にその箱よりも少しだけ大きな箱がある場合、透視投影では奥の箱は手前の箱に隠れてしまいますが、垂直投影では両方の箱が見える状態となります。

文面ではわかりづらいので、[こちら](https://cc.musabi.ac.jp/zoukei_file/03/seizu/NewFiles/touei.html) にある画像を見ていただくのがよいかもしれません。

babylon.js では作成したカメラの `mode` に `Camera.ORTHOGRAPHIC_CAMERA` を設定することで垂直投影カメラに切り替えることができます。

```javascript
var camera = new ArcRotateCamera("camera", 0, 0, 20, new Vector3.Zero(), this.scene);
camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
```

注意点として、光線を平行に投影する形式に変更されるため、投影面の範囲を自分で設定する必要があります。これは各種 `orthoXxx` に値を設定することで行えます。画面のアスペクト比も考慮する必要がありますが、これは `Engine` から取得することができます。

```javascript
var scale = 5;  // スケールの適切な選び方はよくわかっていないので誰か教えてほしい ...
var aspect = scene.getEngine().getAspectRatio(camera);
camera.orthoTop = scale;
camera.orthoBottom = -scale;
camera.orthoLeft = -scale * aspect;
camera.orthoRight = scale * aspect;
```

以下に Multi Canvases を使用して透視投影と垂直投影の２つのカメラを比較するサンプルを示します。左が透視投影、右が垂直投影です。初期状態では表面に凹凸のある地形（リボン）を上から俯瞰していますが、見え方が大きく異なることがわかると思います。

> 画面のアスペクト比がうまく反映できていない感じがしますが、CodePen　のソースコードペインを折り畳むとそれらしい表示になるようです。リサイズイベント等を適切に拾うことで解消できるのかもしれませんが、ここでは対応していません。

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="ZEvOxpG" data-user="dmystk" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/dmystk/pen/ZEvOxpG">
  babylon.js - multi canvases sample</a> by dmystk (<a href="https://codepen.io/dmystk">@dmystk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ちなみに左のキャンバスのカメラをマウス操作すると、連動して右のキャンバスのカメラも動くようになっているので、ご活用ください。なお、連動するのは回転のみで、拡大縮小には対応できていません。