Blenderで3Dモデルを作成し、webpack × Three.js でサイトを作成する

##概要
今回はQiitaのキャラクター（Qiitan）の3Dモデルを作成し、Three.jsで表示するサイト作成まで書いていこうと思います。
長くなってしまいますが、参考になるところがあれば嬉しいです。

##対象
・ Blenderをやってみたい人
・ Three.jsを使って3Dモデルのサイトを作ってみたい人
・ webpackを使った開発環境がある人（※webpackの細かい説明は省略しているため）


##目次
1. バージョンなど
2. Blenderのダウンロード
3. よく使うショートカット
4. BlenderでQiitanを作成
5. ファイル構成
6. Three.jsでglbファイルを読み込む
7. GSAPでアニメーションをつける
8. 完成
9. 参考

##1. バージョンなど
macOS Big Sur v11.4
Blender v2.93.1
Node v16.4.2
vscode  v1.60.0 
webpack v5.27.2
typescript v4.2.3
three.js v0.133.1
gsap v3.8.0

##2. Blenderのダウンロード
こちらからダウンロードできます。
https://www.blender.org/download/

##3. よく使うショートカット
3D制作で非常に使えるショートカットを紹介します。
ショートカットを覚えると大幅な時間短縮ができます。

### オブジェクトモード、編集モード共通
__Aキー__
→オブジェクトの全選択
 
__Xキー__
→削除

__Tabキー__
→オブジェクトモードと編集モードを切り替える

__Gキー__
→対象を移動できる
　→Gキー + x（y,z）でX（y,z）軸に方向にのみ移動させる


__Rキー__
→対象を回転できる
　→Rキー + x（y,z）でx（y,z）軸に方向にのみ回転させる
 

__Sキー__
→対象の拡大、縮小ができる


__Shift + Aキー__
→球体や立方体、平面などを生成する

 
__Shift + Dキー__
→対象物の複製

 
### 編集モードのみ
__Eキー__
→対象の頂点、辺、面を押し出す

 
__Fキー__
→対象に面を張る
 
__Alt（option）+　左クリック__
→周を選択


頂点、辺、面を選択した状態で __Ctrl（command） + r__
→オブジェクトにぐるっと一周辺を入れることができる（ループカット）

##4. BlenderでQiitanを作成
早速作成していきます。
今回作成するQiitanの画像は下記のサイトからお借りしました。
https://suzuri.jp/qiita

こちらを参考に作成します。
![スクリーンショット 2021-11-14 11.58.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/a1183fc6-040b-152e-ec5c-655c239581d6.png)


デフォルトで設置されているカメラ、メッシュ、ライトを削除します。
__Aキー__で全選択し、__Xキー__で削除を選択。
![スクリーンショット 2021-11-12 13.40.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/27d520ed-ac6a-b426-e22c-bb570e76a036.png)

右上のナビゲートで__-Y（画像赤四角）__をクリックし、作業画面を設定します。
__Shift + Aキー__で__画像__から__背景__を選択。
![az.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/35f81343-21d6-1b15-6ff1-ff26616d76eb.png)


Qiitanの画像を追加します。
![スクリーンショット 2021-11-12 13.42.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/71302482-031c-fc3f-79d6-820dbbea7589.png)

__Shift + Aキー__で__メッシュ__から__UV球__を選択。
![スクリーンショット 2021-11-12 13.43.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/a20430f0-f68a-7dbb-9052-0908a5b0b79e.png)

UV球を選択した状態で__Tabキー__で編集モードにします。
右上の__オブジェクトの透明化（画像赤四角）__を有効にして、先程追加した背景画像を見られるようにします。
![aw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/edb694c7-3dc3-6de2-39e6-0e00fc8bff89.png)


UV球を選択した状態で、__Sキー__でUV球をQiitanの全体サイズに合わせます。
![スクリーンショット 2021-11-12 13.44.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/98f598e6-93d3-f520-b7f9-f2bdc621d285.png)

左側半分を選択し、__Xキー__で__頂点__を削除します。
![スクリーンショット 2021-11-12 14.30.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/63c77d79-1535-7bd3-2c5e-1846cceed76a.png)

右側全体を選択し、__モディファイアの追加__から__ミラー__を選択。
![スクリーンショット 2021-11-12 14.30.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/424686d5-07c7-8cc5-1b11-b9df395a5a62.png)

__クリッピング__にチェックを入れます。
これで簡単に左右対称のモデルが作成できるようになります。
![スクリーンショット 2021-11-12 14.31.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/ddd55d60-4ac7-6128-6946-e23384c82ec7.png)

上部真ん中にある__プロポーショナル編集（画像赤四角）__を有効にして、耳を作成します。
（この時右側だけ動かせば自動的に左側も同じように動きます。）
![aq (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/4eaf69da-630c-2065-2511-306627828497.png)


耳を丸くしたいので、__Ctrl + Bキー__でベベルをします。
__幅1m__、__セグメントは5__とします。
![スクリーンショット 2021-11-13 10.29.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/084e1b63-b682-4a15-1aac-4bf0483b3d07.png)

増やした頂点を背景画像に合わせて__Gキー__で移動します。
![スクリーンショット 2021-11-13 10.30.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/a5407911-3905-2e40-cf45-726959b2c8a2.png)

同じ要領で体全体も背景画像に合わせます。
![スクリーンショット 2021-11-13 10.31.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/5b82ce39-a8b9-a3c7-766f-b00b12051dac.png)

ここまでできたらミラーを終了します。
__Tabキー__でオブジェクトモードに変更し、__ミラーの「∨」__から__適用__を選択。
次に尻尾を作っていきます。
![スクリーンショット 2021-11-13 10.37.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/d7108de6-7bc2-9a24-7cf0-8b134b0741d9.png)

__3キー（または左上の赤四角をクリック）__で面選択モードに変更。
右後ろの４面を選択します。
![aa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/d5142dc4-607e-2dd6-1eca-e0a2175daec9.png)


__Iキー__で面を差し込みます。
![スクリーンショット 2021-11-13 10.40.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/f9719745-f68d-bcd0-1860-24b115294b5b.png)

そのまま__Gキー__で面を押し出します。
![スクリーンショット 2021-11-13 10.40.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/c778c015-5d24-d4da-9c85-5db617ed299a.png)

右上のナビゲートで__-Y__をクリックし、作業画面を設定。
尻尾に合わせて__Gキー__で頂点を移動していきます。
__（1キー、または赤四角をクリックで頂点選択モードに変更できます。）__　
![ok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/27337e38-c470-2818-ccff-b57a1a37d73c.png)


頂点が足りないときは__Ctrl + R__（ループカット）で頂点を増やします。
![スクリーンショット 2021-11-13 10.46.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/35337022-da10-9da2-4ed8-e069d64c7171.png)

尻尾はこんな感じで完成です。
![スクリーンショット 2021-11-14 9.55.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/00ce6043-7996-743a-0f46-28683b466e63.png)


__Tabキー__でオブジェクトモードに変更。
__Shift + A__で__メッシュ__から__円__を追加します。
![スクリーンショット 2021-11-14 10.00.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/26e5a204-0091-e94d-87bc-217bd83aa361.png)



X軸方向に90度回転するために
__Rキー__、__Xキー__、__90__の順で入力します。
![スクリーンショット 2021-11-14 10.01.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/9a608123-80ae-949a-b3c4-0c5e3a43ac92.png)



円を選択した状態で__Tabキー__で編集モードにします。
顔の白い部分に合わせて移動します。
![スクリーンショット 2021-11-14 10.03.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/a1d0eed6-e486-5d53-53a0-49942a9bea85.png)



一度__Tabキー__でオブジェクトモードに戻り、
__UV球__と__円__の両方を選択した状態で__Tabキー__で編集モードに変更します。
また、その両方を選択した状態で__メッシュ__から__ナイフ投影__を選択。
![スクリーンショット 2021-11-14 10.03.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/d85055ad-4892-b352-1b0d-9c3b2cd1d2f4.png)


すると円の形にUV球が切り抜けます。
この要領で目やひげも切り抜いていきます。
![スクリーンショット 2021-11-14 10.07.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/4e5e6e75-b86c-38be-a639-8e316ee73280.png)


目やひげ、口、耳を追加し、ナイフ投影します。
![スクリーンショット 2021-11-14 10.53.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/3e787414-3f2e-b080-7e65-834be88dc3b0.png)


ナイフ投影が完了し、各パーツができてきました。
次にマテリアルをつけていきます。
![スクリーンショット 2021-11-14 11.08.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/44c177cf-6864-5f0c-738c-d0a0d5d2b5d8.png)


マテリアルから__新規__を選択
まずは体全体の緑（__#5CC500__）を入力
![スクリーンショット 2021-11-14 11.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/980831b5-d388-e36f-c8fa-2833bedb8600.png)


UV球を__Aキー__で全選択
__割り当て__を選択
![スクリーンショット 2021-11-14 11.08.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/78572562-0a56-a0a5-cc40-9ce68924f6fe.png)


右上のシェーディングから、__マテリアルプレビュー__を選択（右から2番目）
緑色を確認できます。
同じ要領で目や耳も色をつけます。
![スクリーンショット 2021-11-14 11.09.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/9653a5ed-0076-790b-761a-4e0268f28413.png)


白（#FFFFFF）、黒（#000000）、黄色（#E6DB2C）でこのようになります。
![スクリーンショット 2021-11-14 11.24.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/64c0676b-fe8f-60c9-4778-76f677fd8d8a.png)



表面を滑らかにします。
__Tabキー__でオブジェクトモードに変更
UV球を__Aキー__で全選択し、右クリックで__スムースシェード__を追加します。
![スクリーンショット 2021-11-14 11.36.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/2d534d18-0a10-dd8e-bd30-67ff08b2fad6.png)


これで完成です。
![スクリーンショット 2021-11-14 11.38.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/338793e5-d5c1-aee5-5023-4735ff0e1394.png)


Three.jsで読み込むため、glTF形式でエクスポートします。
__ファイル__から__エクスポート__
__glTF2.0(.glb/gltf)__を選択。
![スクリーンショット 2021-11-14 11.39.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/38ed1cec-ee9a-8256-877c-34e8a731f58e.png)


右側の__ジオメトリ__から、__モディファイアを適用__にチェックを入れます。
（他はデフォルトでOK）
ファイル名を入力し、エクスポートします。
![スクリーンショット 2021-11-14 11.39.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/24064ec8-9f6d-cb7c-d966-096f8105a0df.png)


ここからwebpackとThree.jsで読み込んでいきます。

##5. ファイル構成
ファイル構成は下記の通りです。


```
.
├── src
│   └── img
│   └── obj
│   │   └── qiitan.glb
│   │
│   └── scripts
│   │   └── components
│   │   │    └── webgl.ts
│   │   └── app.ts
│   │
│   └── static
│   │   └── favicon.ico
│   │
│   └── styles
│   │   └── base
│   │   │    └── base.scss
│   │   │    └── reset.scss
│   │   └── global
│   │   │    └── functions
│   │   │    │    └── functions.scss
│   │   │    │    └── mixins.scss
│   │   │    │    └── mq.scss
│   │   │    └── setting
│   │   │         └── color.scss
│   │   │         └── font.scss
│   │   │         └── size.scss
│   │   └── project
│   │   └── style.scss
│   │
│   └── index.html
│
├── .eslintrc.js
├── .prettierrc.js
├── package-lock.json
├── package.json
├── tsconfig.json
└── webpack.config.js

```

three.jsは忘れずにインストールしておきます。

```
npm install three @types/three
```

##6. Three.jsでglbファイルを読み込む

Three.jsで3Dを表示するために必要なものは下記の通りです。
① 型の定義と設定
② htmlの設定
③ Sceneの設定
④ Cameraの設定
⑤ Rendererの設定
⑥ Lightの設定
⑦ Renderingの設定
⑧ Loaderの設定

まずはthree.jsを使用できるようにします。

```typescript:webgl.ts
// three.js を使用する
import * as THREE from 'three';
```

次にthree.jsから、glbファイルを読み込むためのGLTFLoaderをインポートします。

```typescript:webgl.ts
// GLTFLoader を使用する
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
```

###① 型の定義と設定

```typescript:webgl.ts
export default class WebGL {
    winSize: {
        [s: string]: number;
    };
    elms: {
        [s: string]: HTMLElement;
    };
    dpr: number;
    three: {
        scene: THREE.Scene;
        renderer: THREE.WebGLRenderer | null;
        clock: THREE.Clock;
        redraw: Group | null;
        camera: THREE.PerspectiveCamera | null;
        cameraFov: number;
        cameraAspect: number;
    };
    srcObj: string;
    constructor() {
        // ウィンドウサイズを取得
        this.winSize = {
            wd: window.innerWidth,
            wh: window.innerHeight,
        };
        // canvas要素を取得
        this.elms = {
            canvas: document.querySelector('[data-canvas]'),
        };
        // デバイスピクセル比(最大値=2)
        this.dpr = Math.min(window.devicePixelRatio, 2);
        // Three.jsに必要な要素
        this.three = {
            scene: null,
            renderer: null,
            clock: null,
            redraw: null,
            camera: null,
            cameraFov: 50,
            cameraAspect: window.innerWidth / window.innerHeight,
        };
        // 作成したqiitanの3Dモデルを読み込む
        this.srcObj = './obj/qiitan.glb';
        this.init();
    }
```

###② htmlの設定

canvasを設置する箇所を指定します。

```html:index.html
<body>
    <main>
        <section>
            <div data-canvas></div>
        </section>
    </main>
</body>
```

###③ Sceneの設定
3D空間の生成を行う設定です。

```typescript:webgl.ts
initScene(): void {
    // シーンを作成
    this.three.scene = new THREE.Scene();
}
```

###④ Cameraの設定
3Dを撮影するためのカメラ設定です。

```typescript:webgl.ts
initCamera(): void {
    // カメラを作成(視野角, スペクト比, near, far)
    this.three.camera = new THREE.PerspectiveCamera(this.three.cameraFov, this.winSize.wd / this.winSize.wh, this.three.cameraAspect, 1000);
    this.three.camera.position.set(0, 0, 9);
}
```

###⑤ Rendererの設定
レンダリングを行うための設定です。

```typescript:webgl.ts
initRenderer(): void {
    // レンダラーを作成
    this.three.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true, // 背景を透明にする
    });
    this.three.renderer.setPixelRatio(this.dpr); // retina対応
    this.three.renderer.setSize(this.winSize.wd, this.winSize.wh); // 画面サイズをセット
    this.three.renderer.physicallyCorrectLights = true;
    this.three.renderer.shadowMap.enabled = true; // シャドウを有効にする
    this.three.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // PCFShadowMapの結果から更に隣り合う影との間を線形補間して描画する
    this.elms.canvas.appendChild(this.three.renderer.domElement); // HTMLにcanvasを追加
    this.three.renderer.outputEncoding = THREE.GammaEncoding; // 出力エンコーディングを定義
}
```

###⑥ Lightの設定
Lightにはたくさん種類があります。
その時に合ったライトを使用するとより良いサイトができます。

```typescript:webgl.ts
setLight() {
    // 環境光源(色, 光の強さ)
    const ambientLight = new THREE.AmbientLight(0x666666);
    this.three.scene.add(ambientLight);
}
```

###⑦ Renderingの設定
3Dを画面に繰り返し描写するための設定です。

```typescript:webgl.ts
rendering(): void {
    // レンダリングを実行
    requestAnimationFrame(this.rendering.bind(this));
    this.three.renderer.render(this.three.scene, this.three.camera);
}
```

###⑧ Loaderの設定
作成した3Dモデルの読み込みを設定します。
読み込み完了後にrenderingを実行しています。

```typescript:webgl.ts
setLoading() {
    // glTF形式の3Dモデルを読み込む
    const loader = new GLTFLoader();
    loader.load(this.srcObj, (obj) => {
        const data = obj.scene;
         // 3Dモデルをredrawに入れる
        this.three.redraw = data;
        // 3Dモデルの初期サイズ設定
        data.scale.set(0.8, 0.8, 0.8);
        // シーンに3Dモデルを追加
        this.three.scene.add(data);
        // レンダリングを開始する
        this.rendering();
    });
}
```

ここまで設定できたら実行します。

```typescript:webgl.ts
init(): void {
    this.initScene();
    this.initCamera();
    this.initRenderer();
    this.setLight();
    this.setLoading();
}
```

ブラウザで確認します。
ばっちり表示できました。
![スクリーンショット 2021-11-14 13.46.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/b4bfa8c5-2456-a9de-67e0-1c41c9b07995.png)


## 7. GSAPでアニメーションをつける


gsapのインストール

```
npm install gsap
```

gsapのインポート


```typescript:webgl.ts
import { gsap } from 'gsap';
```

最後にGSAPでアニメーションをつけていきます。

### Renderingの設定に追加

```typescript:webgl.ts
rendering(): void {
    // レンダリングを実行
    requestAnimationFrame(this.rendering.bind(this));
    this.three.renderer.render(this.three.scene, this.three.camera);
    // アニメーション開始
    this.animate();// 追加
}
```

### アニメーションの設定
例えばY軸に回転させてみます。

```typescript:webgl.ts
animate() {
    gsap.config({
        force3D: true,
    });
    const tl = gsap.timeline({
        paused: true,
        defaults: {
            duration: 0.6,
            ease: 'power2.easeOut',
        },
    });
    // 回転させる
    tl.to(this.three.redraw.rotation, {
        duration: 1.6,
        y: 6,
    });
    tl.play();
}
```


ブラウザで確認してみると、回転していることがわかります。
![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/f2f67d2e-d74b-d0a4-4669-dd09be331427.gif)


位置やサイズは下記のようにすると設定できます。
チェックしながら好きなアニメーションを設定していきます。

```typescript:webgl.ts
animate() {
    gsap.config({
        force3D: true,
    });
    const tl = gsap.timeline({
        paused: true,
        defaults: {
            duration: 0.6,
            ease: 'power2.easeOut',
        },
    });
    // 回転させる
    tl.to(this.three.redraw.rotation, {
        duration: 1.6,
        y: 6,
    });
    // サイズを変更する
    tl.to(this.three.redraw.scale, {
        duration: 1.6,
        x: 3,
        y: 3,
        z: 3,
    });
    // 位置を変更する
    tl.to(this.three.redraw.position, {
        duration: 1.6,
        x: 3,
        y: 3,
        z: 3,
    });
    tl.play();
}
```

##8. 完成
好きなアニメーションやテキストをつけて完成です。
![ogp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2276913/b2317e09-6bc7-7c76-0772-f55007132421.png)

今回作成したサイトが下記になります。
https://kaito-takase.dev/3d-qiita/

Gitにソースコードを置いているので、参考になるところがあればお願いします。
https://github.com/kaitoooo/3d-qiita

予想通り長くなってしまいましたが、ここまで読んでいただきありがとうございました。


##9. 参考
https://suzuri.jp/qiita