Babylon.jsで始める3Dコンテンツ開発入門

<img width="500" alt="bamv-logo" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/01129bb2-405d-0a76-f79d-1dbae0d43224.png"> 

# 概要

ブラウザ上で手軽に3Dコンテンツを動かしてみたいと思い、最近話題のBabylon.js を触ってみた、という記事です。
この記事は業務の一環として執筆しているため、弊社(BAMV合同会社)のロゴを3Dで作ってみました。

## 最終的にできたもの
https://bamv-3d.netlify.app/

# Babylon.jsとは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/3ea1445f-848d-607e-0ccf-d1f4704815e5.png)

[Babylon.js](https://www.babylonjs.com/)(ばびろんじぇーえす)とは、Microsoft社が開発しており、JavaScriptで3Dモデルをレンダリングすることができるライブラリです。
 [Babylonjs Playground](https://playground.babylonjs.com/)という開発環境が用意されているため、ローカルで環境構築をしなくても触ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/7088a2ae-d93c-eadb-8a67-073574fa3f01.png)
[公式チュートリアル](https://doc.babylonjs.com/start)も充実しています。

# 基本設定

> 神は言われた。「光あれ。」こうして、光があった。 （『創世記』1:3）

詳しいパラメータ設定は[公式ドキュメント](https://doc.babylonjs.com/typedoc/classes/BABYLON.ArcRotateCamera#constructor)をご参照ください。

## シーン、カメラ、ライトを作成

``` js
// シーンを作成
const scene = new BABYLON.Scene(engine);
// カメラを作成(カメラの向き、ズームを設定)
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 5, new BABYLON.Vector3(0, 0, 0), scene);
camera.attachControl(canvas, true);
// ライトを作成(ライトの向きを設定)
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/04f13b47-d7e7-7b7c-65a1-38f906cbc73b.png)
まだ何も見えません。

## 地面を作成

``` js
  const ground = BABYLON.MeshBuilder.CreateGround("ground", {
    width: 10,
    height: 10,
  });
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/7c8d4343-777f-d68e-34a4-beab2c954380.png)
白い平面が出ました。

## 地面にテクスチャを設定

地に草を生やします。textureをmaterialに設定し、materialをgroundに設定します。この概念は他でも使います。

``` js
  const grassMaterial = new BABYLON.StandardMaterial("grassMat");
  const grassTexture = new BABYLON.GrassProceduralTexture("grassTex", 256);
  grassMaterial.ambientTexture = grassTexture;
  ground.material = grassMaterial;
```

ここまででできたもの:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/e248f7ca-9130-4f98-a482-664e0976382e.png)

https://playground.babylonjs.com/#CIGKRZ
天地が創造されたので、この世界に好きな物を置けるようになりました。

# ロゴ作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/6348446a-d3bd-21aa-a6bb-9a0d6809f42e.png)

## 流れ

1.輪郭をXZ平面上に座標を指定して描く
この世界の座標軸 (x:横, z:縦, y:高さ のようなイメージ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/30046c16-b1ef-1e2f-7a37-c11e335119ba.png)

2.Y軸方向に高さを設定
3.X軸方向を回転軸として90°回転させて縦にする

## ロゴマーク

上部のマークの部分を作成します。
形を見ると、左と右に分けて作れそうです。それぞれ作った後にマージし、色を付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/218e03bc-9221-37ac-0c06-3673f3e9f41b.png)

### 左側
``` js
  // 輪郭の座標を配列に入れる
  const pointsL = [
    new BABYLON.Vector3(0, 0, 0),
    new BABYLON.Vector3(0, 0, 0.48),
    new BABYLON.Vector3(0.1, 0, 0.6),
    new BABYLON.Vector3(0.1, 0, 0.19),
    new BABYLON.Vector3(0.17, 0, 0.19),
    new BABYLON.Vector3(0.17, 0, 0),
  ];
  // メッシュを作成
  const logoL = BABYLON.MeshBuilder.ExtrudePolygon("logoL", {
    shape: pointsL,
    // 高さ
    depth: 0.2,
    sideOrientation: BABYLON.Mesh.DOUBLESIDE,
  });
```

### 右側
``` js
  const pointsR = [
    new BABYLON.Vector3(0.1, 0, 0.6),
    new BABYLON.Vector3(0.1, 0, 0.72),
    new BABYLON.Vector3(0.28, 0, 0.94),
    new BABYLON.Vector3(0.28, 0, 0.19),
    new BABYLON.Vector3(0.17, 0, 0.19),
    new BABYLON.Vector3(0.17, 0, 0.69),
  ];
  const logoR = BABYLON.MeshBuilder.ExtrudePolygon("logoR", {
    shape: pointsR,
    depth: 0.2,
    sideOrientation: BABYLON.Mesh.DOUBLESIDE,
  });

```

### マージして色を付ける
  - マージすると一つのオブジェクトとして扱うことができます。
  - MergeMeshesの引数にいろいろ設定できます。(https://doc.babylonjs.com/divingDeeper/mesh/mergeMeshes)
  - マテリアルの設定方法は先程と同様です。
``` js
    // マージ
    const logo = BABYLON.Mesh.MergeMeshes(
    [logoL, logoR],
    true,
    false,
    null,
    false,
    true
  );

  const logoMat = new BABYLON.StandardMaterial("logoMat");
  logoMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
  logo.material = logoMat;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/debe4a2c-e121-3f80-c6c9-1b531b4d2cbb.png)

地面にロゴマークが描かれました。地面の草と重なってしまっているため、ちらつきが発生しています。


### 位置を調整

``` js
  // x軸方向を回転軸として90°回転して縦にする
  logo.rotation = new BABYLON.Vector3(-Math.PI / 2, 0, 0);
  //  浮かせる
  logo.position.y = 0.6;
```

ここまでの進捗: 

https://playground.babylonjs.com/#CIGKRZ#2 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/ba5af8b1-a68a-eb32-fe53-e4e6a9a38a44.png)



## 会社名

### **B**を作成
まずBの下半分(Dみたいな形)を作り、その後クローンし2つ重ねて作ります。
Dを作るには、輪郭を作った後穴を開けます。
`ExtrudePolygon`の`shape`に輪郭の座標を設定し、`holes`に穴を開けたい部分の座標を設定します。
また、曲線部分の描画には皆さんも大好きな三角関数を使って描画することができます。単位円を頭に思い浮かべながら実装しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/b122f9ca-e01b-758f-105d-f3990ec2e5f2.png)
- 輪郭の座標を定義
``` js
  const pointsB = [
    new BABYLON.Vector3(0, 0, 0),
    new BABYLON.Vector3(0, 0, 0.14),
    new BABYLON.Vector3(0.1, 0, 0.14),
  ];
  // 曲線部分
  for (let i = 20; i > -20; i--) {
    pointsB.push(
      new BABYLON.Vector3(
        0.07 * Math.cos((i * Math.PI) / 40) + 0.1,
        0,
        0.07 * Math.sin((i * Math.PI) / 40) + 0.07
      )
    );
  }
  pointsB.push(new BABYLON.Vector3(0.1, 0, 0), new BABYLON.Vector3(0, 0, 0));
```

- 穴の座標を定義
```js
  // 穴の座標
  const holes = [];
  holes[0] = [];
  holes[0].push(new BABYLON.Vector3(0.03, 0, 0.03));
  holes[0].push(new BABYLON.Vector3(0.03, 0, 0.11));
  holes[0].push(new BABYLON.Vector3(0.1, 0, 0.11));
  for (let i = 20; i > -20; i--) {
    holes[0].push(
      new BABYLON.Vector3(
        0.04 * Math.cos((i * Math.PI) / 40) + 0.1,
        0,
        0.04 * Math.sin((i * Math.PI) / 40) + 0.07
      )
    );
  }
  holes[0].push(new BABYLON.Vector3(0.1, 0, 0.03));
  holes[0].push(new BABYLON.Vector3(0.03, 0, 0.03));
```

- Bの半分を作る
```js
  // Bの半分を作成
  const b_half1 = BABYLON.MeshBuilder.ExtrudePolygon("B1", {
    shape: pointsB,
    holes: holes,
    depth: 0.1,
    sideOrientation: BABYLON.Mesh.DOUBLESIDE,
  });
```
Dみたいな形ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/8d08ea86-1959-5db3-ce89-93348148cef7.png)


- クローンし重ねてBにする

```js
  // cloneする
  const b_half2 = b_half1.clone("B2");
  // クローンの位置を調整
  b_half2.position.z = 0.115;
```


Bができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/f671b873-fc38-20cf-1248-e9e952ce4a8c.png)

https://playground.babylonjs.com/#DU9AYJ

三角関数は不要である等と発言して炎上した人がいたそうですが、「三角関数、世の中の役に立ってるんだなあ」と実感しました。

### 同様にA, M, Vも作成
> 神はそれらのものを祝福して言われた。
「産めよ、増えよ、海の水に満ちよ。鳥は地の上に増えよ。」
(創世記 1:22)

Bと同じなので省略します。

### 文字をまとめる

```js
  // マージ処理
  const companyName = BABYLON.Mesh.MergeMeshes(
    [b_half1, b_half2, a, m, v],
    true,
    false,
    null,
    false,
    true
  );
  // 色をつける
  const companyNameMat = new BABYLON.StandardMaterial("companyNameMat");
  companyNameMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
  companyName.material = companyNameMat;
  // 縦にする
  companyName.rotation = new BABYLON.Vector3(-Math.PI / 2, 0, 0);
  // 位置調整
  companyName.position.x = -0.2;
  companyName.position.y = 0.2;
  companyName.position.z = 0.1;
```

ロゴができました。

https://playground.babylonjs.com/#CIGKRZ#3

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/39ec53dc-58ae-6887-6407-d43ef5d95e8e.png)

# (おまけ)アニメーションをつける
参考: [Designing Animations](https://doc.babylonjs.com/divingDeeper/animation/animation_design)
以下のように移動方向や移動量、フレームレートを定義することで簡単に動きをつけることができます。

```js
    const frameRate = 10;
    //Position Animation
    const xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    const keyFramesP = []; 
    keyFramesP.push({
        frame: 0,
        value: 1
    });
    keyFramesP.push({
        frame: frameRate,
        value: -1
    });
    keyFramesP.push({
        frame: 2 * frameRate,
        value: 1
    });
    xSlide.setKeys(keyFramesP);

    //Rotation Animation
    const yRot = new BABYLON.Animation("yRot", "rotation.y", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    const keyFramesR = []; 
    keyFramesR.push({
        frame: 0,
        value: 0
    });
    keyFramesR.push({
        frame: frameRate,
        value: -2 * Math.PI
    });
    keyFramesR.push({
        frame: 2 * frameRate,
        value: -4 * Math.PI
    });
    yRot.setKeys(keyFramesR);

    scene.beginDirectAnimation(completeLogo, [xSlide, yRot], 0, 2 * frameRate, true);
```

https://playground.babylonjs.com/#CIGKRZ#4

![logo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524398/66e7587b-0685-2d51-07cf-cb1cf7c0672c.gif)


# おわりに
本記事で紹介したのはBabylon.jsの機能のごく一部であり、他にも様々な機能があります。ドキュメントが充実していて仕様もわかりやすいので、慣れればさらに幅広い種類のコンテンツを作成できそうです。
Three.js等も含めWebGL関連の開発は日々進歩しており、今後のフロントエンド開発にはより一層こうした技術が取り入れられていくかもしれません。
皆さんもwebブラウザ上に手軽に自分の世界を創造してみてください。