babylon.js で Material を自作する方法

WebGL フレームワークである [babylon.js](https://www.babylonjs.com/) には、モデルの材質表現を扱うクラスとして `Material` があります。

- [Materials - Babylon.js Documentation](https://doc.babylonjs.com/babylon101/materials)
- [Configurable Materials - Babylon.js Documentation](https://doc.babylonjs.com/features/materials)

このあたりのドキュメントで概要がつかめると思いますが、テクスチャや色などのパラメータを割り当てて(bind)、ポリゴンがどういう風に光を受け描画されるかを設定することが出来る機能です。

デフォルトでは [StandardMaterial](https://doc.babylonjs.com/api/classes/babylon.standardmaterial) というマテリアルが適用されますが、これには様々な機能が搭載されています。

<p class="codepen" data-height="265" data-theme-id="0" data-default-tab="js,result" data-user="akai_inu" data-slug-hash="pmXLZJ" data-preview="true" 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 Variable Materials">
  <span>See the Pen <a href="https://codepen.io/akai_inu/pen/pmXLZJ/">
  babylon.js Variable Materials</a> by 山岸 "あかいいぬ?" Masaru (<a href="https://codepen.io/akai_inu">@akai_inu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

例えばこのような感じで、手軽にマテリアルを設定可能です。視差マッピングがデフォルトであるのも面白いです。

これらのマテリアルは `WebGL Shader` によって実現されています。マテリアルと 1 対 1 でシェーダーが [Effect](https://doc.babylonjs.com/api/classes/babylon.effect) クラスの中に用意されています。

実行時はこの `Effect` を使って、割り当てられたメッシュをレンダリングしていきます。

また、 [物理ベースレンダリングマテリアル](https://doc.babylonjs.com/how_to/physically_based_rendering)も実装されているので、 glTF で PBR 設定がされているものに関しては自動で適用されます。

さらに、別パッケージとして [Materials Library](https://github.com/BabylonJS/Babylon.js/tree/master/materialsLibrary) があり、こちらで溶岩や水、セルシェーディングなどのカスタムマテリアルが用意されています。自作 Material を作る際の参考になりますね。

今回は私が実装した [babylon-mtoon-material](https://www.npmjs.com/package/babylon-mtoon-material) を作る際に参考にしたこと、注意したことをまとめておきます。

事前に、 WebGL シェーダを理解する必要があるので、 [Introduction to Shaders - Babylon.js Documentation](https://doc.babylonjs.com/resources/shaderintro) を読みます。今回は WebGL シェーダについての紹介は省略します。

## Material を自作する方法

1. CYOS を使う: ブラウザで手軽にシェーダーを書いてそれを ShaderMaterial としてエクスポートして利用
2. ShaderMaterial を使う: 任意のシェーダーを読み込んで利用
3. Material Library を使って拡張する: 公式の拡張マテリアル一覧に新しいマテリアルを追加して利用
4. CustomMaterial を使って拡張する: StandardMaterial の特定のシェーダー部分を置換して利用
5. 完全新規に作成する: Material を一から作成
6. [WIP]NodeMaterial を使って視覚的に作成する

## 1. CYOS を使う

[Put Shader Code in BJS - Babylon.js Documentation](https://doc.babylonjs.com/how_to/putting)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332028/997fd500-1726-ae32-3db2-b16c42873cec.png)

babylon.js の [Create Your Own Shader](http://www.babylonjs.com/cyos/) ページを利用すると、リアルタイムにシェーダーの変更を反映させながらシェーダーを作ることが出来ます。

結果を zip 出力すると、後述する ShaderMaterial を使って、書いたシェーダーを読み込めるようになります。

これは非常に簡単にシェーダーをいじることが出来るので楽ですが、シェーダーに入力する値を変更出来ないので拡張しづらいです。

## 2. ShaderMaterial を使う

[Use ShaderMaterial - Babylon.js Documentation](https://doc.babylonjs.com/how_to/shader_material)

ShaderMaterial を利用することで、任意のシェーダー文字列を読み込み、値をバインドし、動作させることが出来ます。

1. の CYOS でもこの ShaderMaterial 用のスクリプトが出力されます。

```typescript
// ファイル ./cyos.vertex.fx と ./cyos.fragment.fx を読み込むマテリアルを作成する
const shaderMaterial = new BABYLON.ShaderMaterial('cyos', scene, './cyos');

// <script type="application/vertexShader" id="cyosVertexShaderCode">
// <script type="application/fragmentShader" id="cyosFragmentShaderCode">
// のタグに書かれたシェーダを読み込むマテリアルを作成する
const shaderMaterial2 = new BABYLON.ShaderMaterial('cyos2', scene, { vertexElement: 'cyosVertexShaderCode', fragmentElement: 'cyosFragmentShaderCode' });

// 文字列からマテリアルを作成する
BABYLON.Effect.ShadersStore['cyosVertexShader'] = `
precision highp float;

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
    gl_Position = worldViewProjection * vec4(position, 1.0);

    vUV = uv;
}
`;
BABYLON.Effect.ShadersStore['cyosFragmentShader'] = `
precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
    gl_FragColor = texture2D(textureSampler, vUV);
}
`;
const shaderMaterial3 = new BABYLON.ShaderMaterial('cyos3', scene, { vertex: 'cyos', fragment: 'cyos' });
```

最後の `Effect.ShadersStore` にマテリアル文字列を代入してそれを読み込む方法が、環境に依存しなくなるので安全ですね(静的変数なので、名前だけ被らないように注意)。

## 3. Material Library を使って拡張する

[Create a Material for the Materials Library - Babylon.js Documentation](https://doc.babylonjs.com/how_to/how_to_create_a_material_for_materialslibrary)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332028/43538431-5fb7-77e8-e714-e384f8363d22.png)

パッケージは [@babylonjs/materials](https://www.npmjs.com/package/@babylonjs/materials) となっていますが、同じリポジトリで開発されている [materialsLibrary](https://github.com/BabylonJS/Babylon.js/tree/master/materialsLibrary) を拡張する形で、新しいマテリアルを実装出来ます。

まず、 [BabylonJS/Babylon.js](https://github.com/BabylonJS/Babylon.js/) リポジトリ自体を git clone してきます(かなり重いので `git clone --depth 1` で shallow clone する方が良いかもしれません)。

`/Tools/Gulp` ディレクトリに移動し、下記を打ちます。

```shell
Babylon.js/Tools/Gulp $ npm install --global gulp
Babylon.js/Tools/Gulp $ npm install
...
Babylon.js/Tools/Gulp $ npm start
```

これで `http://localhost:1338/materialsLibrary/` にアクセスすることで上記の画像のページを開くことが出来ます。

このページでは、 `/materialsLibrary/src` 内に記述された各種マテリアルを、任意のパラメータと共にテストすることが出来ます。

`/materialsLibrary/index.html` を直接書き換えて自分のマテリアルを UI に表示する必要があるのが少し面倒ですが、比較的手軽にいくつかのメッシュに対してテストが可能になります。

## 4. CustomMaterial を使って拡張する

`materialsLibrary` の中にある [CustomMaterial](https://github.com/BabylonJS/Babylon.js/blob/master/materialsLibrary/src/custom/customMaterial.ts) を利用すると、 `StandardMaterial` のシェーダコードの特定の部分を置換し、 `StandardMaterial` に処理を加えることが出来るようになります。

正直、 `StandardMaterial` 自体を詳しく把握しないと追記することが難しいので、上級者向け(というか使う場所あるか？)です。

## 5. 完全新規に作成する

マテリアルは `Material` クラスを継承していればなんでも良いので、 babylon.js が用意している環境に依存せず単体で作成していくことももちろん可能です。

[babylon-mtoon-material](https://www.npmjs.com/package/babylon-mtoon-material) は上記を検討した結果、ここにたどり着きました。

シェーダは webpack の [raw-loader](https://github.com/webpack-contrib/raw-loader) を利用して文字列として `import` 出来るようにし、 Material のコンストラクタで `Effect.ShadersStore` に代入しています。

また、 babylon.js 独自の仕様で、シェーダ内に `#include<instancesDeclaration>` などと書くことで別のファイルを読み込むことが出来ます(GLSL 系列は include 処理がないので、シェーダのコンパイル前にファイルを取得してきて文字列連結させています)。

include 出来るファイルは `Effect.IncludesShadersStore` で管理されます。

### マテリアルの値のテストを行う

開発時は、マテリアルに渡す値(色やテクスチャなど)をリアルタイムに変更して、見た目がどうなるか確認したくなります。

materialsLibrary のページではその機能がありましたが、単体でマテリアルを作成した場合は全て自前で GUI を用意しなければなりません。

それは面倒なので、 babylon.js のインスペクタを拡張することで実現しました。

[Display and Use the Inspector - Babylon.js Documentation](https://doc.babylonjs.com/how_to/debug_layer)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332028/30b17675-37f4-4fb6-b47e-805488e79ff0.png)

`material.inspectableCustomProperties` というプロパティに変更可能なプロパティを列挙することで、上記画像のようにインスペクタから値を変更することが出来るようになりました。

現在この方式でサポートされているのは `Checkbox, Slider, Color3, Vector3, Quaternion` だけですが、ある程度はカバーできるのではないでしょうか。

テクスチャなどカバー出来ない範囲は別のメッシュに対してマテリアルを適用させて、それで確認出来るようにしています。

[Babylon.js MToon Material Test](https://virtual-cast.github.io/babylon-mtoon-material/?inspectorshadow)

## 6. [WIP] NodeMaterial を使って視覚的に作成する

現在 v4.1 向けに、 Unity の ShaderGraph や UE4 の Node Graph のようなノードベースのマテリアル作成機能が開発中のようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332028/8fa4e9cc-4339-a360-6b85-b8924abd8839.png)

Inspector と同様 React ベースのエディタが搭載されるようです。

ref. [node based material - Questions - Babylon.js](https://forum.babylonjs.com/t/node-based-material/1739/8)

[Update: 2019/07/04] author の david さんが NodeMaterial についての記事を medium に投稿していました。
[Creating the Babylon.js Node Material - Babylon.js - Medium](https://medium.com/@babylonjs/creating-the-babylon-js-node-material-6893b3abe4df)

---

現状では新しいマテリアルを作るのは結構大変です。 StandardMaterial が出来ることも移植しようとすると、 babylon.js と WebGL の知識がそれなりに必要になります。

ノードベースのマテリアル作成ツールがリリースされて、わかりやすくマテリアルが作成出来るようになれば良いですね。