【React Three Fiber】Blenderを使用したメッシュのMorphing

<!-- ================================================ -->
# 概要
<!-- ================================================ -->
**Morph**とは、メッシュの形状を流動的に変更することを指します。

![output(video-cutter-js.com) (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/cd59a937-4738-dcc5-8ae5-13da62543a60.gif)

React Three Fiberのみでこれを実現させようとすると、頂点の座標設定など煩雑なコーディングが必要です。

:::note info
一方、Blenderで<strong>Shape Key</strong>を設定したモデルをインポートすることで、簡単にMorphを表現することができます。
:::

### サンプル
実際に動くものを触ってみたい方はこちら。

https://codesandbox.io/s/morphing-glb-mesh-01rs0

※ この記事とは扱ってるモデルが少し異なります。

<!-- ================================================ -->
# Shape Keyの設定
<!-- ================================================ -->
Blenderを使用して**Shape Key**を設定します。

![スクリーンショット 2021-11-08 085053.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/6d418681-7499-0ec9-3a57-3c87cac7a1a0.png)

### Shape Keyの追加
[+]を押すことで、キーを追加します。

![スクリーンショット 2021-11-08 085614.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/96140fc1-4100-1987-e7eb-fedc8c35b629.png)

* 最初の**Basis**は、基準となる形状です。形状を変更していくのは**Key 1**です。

### 形状の変更
**Key 1**を選択した状態で、メッシュの形状を[編集モード]で変更します。

:::note warn
ここで注意点として、
<font color='red'>メッシュの頂点数を変更すると、Morphできなくなります。</font>
Cubeの頂点数は8なので、この頂点数8を変えないように形状変更します。
:::

![スクリーンショット 2021-11-08 090052.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/c86cde95-29f1-00b7-2889-59c6fb1b6617.png)

* 上図では、Cubeの上面の頂点を重ねているだけなので、頂点数は8のままです。

### Shape Keyの確認
編集モードで形状の変更ができたら、オブジェクトモードに戻ります。
オブジェクトモードに戻ると、メッシュの形状はBasisの状態（変更前の状態）になります。

**Key 1**の値（Value）を変更することで、メッシュをMorphさせることができます。
値の範囲は、0～1です。

**値：0**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/673e9df4-c0c0-da3a-9448-d8f03b56fe21.png" width="500">

**値：0.5**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/a37947f4-fa7d-4b69-f600-36e9e949195d.png" width="500">

**値：1**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/58947d68-cba5-724e-e508-c32ec7d4abb8.png" width="500">

### ブレンド
Shape Keyで変更される形状は、複数を組み合わせることができます。
例えば、Keyをもうひとつ追加します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/00e5c3b3-82fa-b78a-21f6-97926625a05b.png" width="500">

Key 1とKey 2の値をそれぞれ1にすると、形状がブレンドされることがわかります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/7892fd8d-d404-6629-e7ab-aa406ab161a0.png" width="500">

<!-- ================================================ -->
# glbエクスポート
<!-- ================================================ -->
作成したモデルを**glbファイル**としてエクスポートします。
この例では、**morph.glb**という名前で保存しました。
![スクリーンショット 2021-11-08 093018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/73ebb765-bf2f-3cd0-416b-8015a6196d87.png)
![スクリーンショット 2021-11-08 093208.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/97c97fef-66ba-6d5a-1ba6-399256675ae8.png)

<!-- ================================================ -->
# React Three Fiberへのインポート
<!-- ================================================ -->
### Morph.tsx
Canvasコンポーネントを生成するためのファイル（Morph.tsx）を作成します。

```.tsx:Morph.tsx
import React, { Suspense, VFC } from 'react';
import { OrbitControls, Stats } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { MorphModel } from './MorphModel';

export const Morph: VFC = () => {
	return (
		<Canvas
			camera={{
				position: [0, 5, 10],
				fov: 50,
				aspect: window.innerWidth / window.innerHeight,
				near: 0.1,
				far: 2000
			}}
			dpr={window.devicePixelRatio}
			shadows>

			{/* background */}
			<color attach="background" args={['#1e1e1e']} />
			{/* fps */}
			<Stats />
			{/* control */}
			<OrbitControls />
			{/* light */}
			<directionalLight position={[0, 5, 5]} />
			<ambientLight intensity={0.1} />

			{/* mesh */}
			<Suspense fallback={null}>
				<MorphModel />
			</Suspense>
		</Canvas>
	)
}
```
* **background**では、Canvasの背景色を設定しています。
* **fps**では、画面左上にFPSチェッカーを追加しています。
* **control**では、カメラのコントローラーを追加しています。マウス操作によりカメラを移動することができます。
* **light**では、シーンのライティングをしています。
* **mesh**では、glbファイルを読み込みを行っています。glbファイルを読み込む場合は必ず**Suspense**で囲う必要があります。MorphModelは別ファイルに定義しています。

### MorphModel.tsx
glbファイルのインポートファイル（MorphModel.tsx）を作成します。

#### 雛形コードの生成
まず、雛形となるコードを生成します。
以下のサイトに、glbエクスポートをした**morph.glb**をドロップすると、glbファイルをインポートするためのコードの雛形が自動で生成されます。

https://gltf.pmnd.rs/

画面右上のコントローラーから、必要な項目を設定します。
特に、TypeScriptでコーディングしている場合は、**types**にチェックを入れましょう。

![Inkedスクリーンショット 2021-11-08 100709_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/f56e83d1-6375-7ca5-8eb5-15779398f92b.jpeg)

ここで、**morphTargetプロパティ**があれば、設定したShape Keyがちゃんとエクスポートされています。
最後に、**[copy to clipboard]**でコピーします。

:::note info
雛形ファイルは、コマンドから生成することもできます。
glbファイルの容量の大きく上記のサイトで表示が遅い場合は、こちらが使えます。
:::

https://github.com/pmndrs/gltfjsx

#### glbファイルの追加
プロジェクトにエクスポートした**morph.glb**を追加します。
この例では、以下に配置しました。

```shell
public/assets/morph.glb
```

#### インポートファイルの作成
コピーしたひな形を元に読み込みファイルを作成します。

```.tsx:MorphModel.tsx
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/

import { useControls } from 'leva';
import React, { useRef, VFC } from 'react';
import * as THREE from 'three';
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import { useGLTF } from '@react-three/drei';

type GLTFResult = GLTF & {
	nodes: {
		Cube: THREE.Mesh
	}
	materials: {
		Material: THREE.MeshStandardMaterial
	}
}

const ModelPath = '/assets/morph.glb'

export const MorphModel: VFC = (props: JSX.IntrinsicElements['group']) => {
	const group = useRef<THREE.Group>()
	const { nodes, materials } = useGLTF(ModelPath) as GLTFResult

	// コントローラーの追加
	const datas = useControls({
		key1: {
			value: 0,
			min: 0,
			max: 1,
			step: 0.01
		},
		key2: {
			value: 0,
			min: 0,
			max: 1,
			step: 0.01
		}
	})

	console.log(nodes.Cube.morphTargetDictionary)
	console.log(nodes.Cube.morphTargetInfluences)

	return (
		<group ref={group} {...props} dispose={null}>
			<mesh
				name="Cube"
				castShadow
				receiveShadow
				geometry={nodes.Cube.geometry}
				material={materials.Material}
				morphTargetDictionary={nodes.Cube.morphTargetDictionary}
				// morphTargetInfluences={nodes.Cube.morphTargetInfluences}
				morphTargetInfluences={[datas.key1, datas.key2]}
			/>
		</group>
	)
}

useGLTF.preload(ModelPath)
```
* **ModelPath**は、morph.glbへのパスを定義しています。

```.tsx
const ModelPath = '/assets/morph.glb'
```

* console.logで、**morphTargetDictionary**と**morphTargetInfluences**に何が入っているか確認します。

```.tsx
console.log(nodes.Cube.morphTargetDictionary)
console.log(nodes.Cube.morphTargetInfluences)
```

![スクリーンショット 2021-11-08 102642.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/c6aa8614-1fcd-0b73-0910-0111bb13a7c8.png)

`morphTargetDictionary`には、Blenderで設定した**Shape Keyの名前**と**定義の順番**がオブジェクトで格納されています。
`morphTargetInfluences`には、Shape Keyそれぞれに対する、**morphの値**が配列で格納されています。この格納順が定義の順番にあたります。初期値は0になっているので、読み込んだメッシュは変形していません。**配列の値を0～1まで変化させることで、Morphingさせることができます。**

* コントローラーを追加して、値を変更できるようにします。

```.tsx
const datas = useControls({
	key1: {
		value: 0,
		min: 0,
		max: 1,
		step: 0.01
	},
	key2: {
		value: 0,
		min: 0,
		max: 1,
		step: 0.01
	}
})
```
```.tsx
// morphTargetInfluences={nodes.Cube.morphTargetInfluences}
morphTargetInfluences={[datas.key1, datas.key2]}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/247d2d50-9030-6f0a-6340-e915481d7e76.png" width="600">

コントローラーには、**leva**パッケージを使用しています。levaについて詳しく知りたい方は、以下を参照してください。

https://qiita.com/nemutas/items/a5efdd1e8f2040f6e6e5

<!-- ================================================ -->
# Tips
<!-- ================================================ -->
### glbエクスポートの仕様

https://docs.blender.org/manual/en/2.80/addons/io_scene_gltf2.html

### React Three Fiberの記事
過去に投稿した記事です。ご参考までに。

| 投稿日 | タイトル・リンク |
|:-|:-|
| 2021.10.23 | [【React】react-three-fibarで3D表現をする（オブジェクトのClippingと断面）](https://qiita.com/nemutas/items/d9803a062b2968e7099d) |
| 2021.09.22 | [【React】react-three-fibarで3D表現をする（Mixamoを使ったアニメーションモデル）](https://qiita.com/nemutas/items/6202b3f8458376ab79b6) |
| 2021.09.18 | [【React】react-three-fibarで3D表現をする（Blenderで作成したモデルの読み込み）](https://qiita.com/nemutas/items/6d89cb809d51fa8247ad) |
| 2021.09.18 | [【React】react-three-fibarで3D表現をする（文字表示）](https://qiita.com/nemutas/items/b7754449e65071b821e9) |
| 2021.09.17 | [【React】react-three-fibarで3D表現をする](https://qiita.com/nemutas/items/60587fb3c67450ecff6d) |
| 2021.06.11 | [【React × Three.js】Blenderで作成したモデルをReactプロジェクトに読み込む](https://qiita.com/nemutas/items/27a8e961dd8f65360b0b) |