UE4でVR空間にカノジョを錬金 24日目

[UE4でVR空間にカノジョを錬金する Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ue4vrgame-create) 24日目の記事です。

祝！クリスマスイブ！！

12/1～25日までに一人で、UE4を用いてサ○○レッスンとかVR○○ジョっぽいゲームを作って仮想彼女を生み出すことにチャレンジします。

今回はボイス再生に合わせたリップシンクを実装していきます。
かなり四苦八苦しましたが、何とか実装できました。

本日と明日、二回に分けて記述していきます。

# 参考にしたサイト

みんな大好き凹みTipsからリップシンク関連の記事を大いに参考、利用させていただいております。

## リップシンク実装　初期時

最初は[フォルマントから母音推定してリップシンクを目指してみる](http://tips.hecomi.com/entry/20131110/1384096497)と[Unity でリアルタイムにリップシンクする MMD4Mecanim LipSync Plugin を作ってみた](http://tips.hecomi.com/entry/20131208/1386514048)参考に、UE4でボイスデータを解析し、フォルマントからの母音抽出を行ってリアルタイムにモーフターゲットに反映する実装検証をしてました。

ソースは[MMD4Mecanim-LipSync-Pluginソース](https://github.com/hecomi/MMD4Mecanim-LipSync-Plugin)を参考にBluePrintで処理を実装、UE4はデフォルトでは音の周波数が取れないため、公式プラグインのSound Visualizations Pluginを使用しました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/1a991448-5481-4615-0bc8-4e4bda8e7b20.png)

ところがこのプラグイン、どうやらエディタ－でしか周波数が取れないらしく、パッケージングするとエラーとなってしまうようです。

ならば周波数解析のライブラリを利用してUE4プラグインを作ろうとしましたが、そもそもc++ライブラリを一回も使用したことがなかったために実装が思うようにいかず、最終的にプラグイン作成をあきらめました。

## リップシンク実装　実際にどうしたか

今回の実装目的を改めて見直しました。

実装するにあたり欲しいのは、「ユニティちゃん（グレイちゃん）ボイスデータのリップシンクに必要な、各モーフターゲットに入れる値のデータ」です。

リアルタイムで入力する合成音声を使用していない以上「ボイスファイルの解析データ」、極論を言えば「モーフターゲットに入れる値のリストデータ」さえあれば、別にリアルタイムで音を解析する必要が無いと思い当たりました。

そこで考えた結果、ある結論に至りました。

MMD4Mecanim-LipSync-PluginソースをいじってUnityから解析データをCSVで吐き出せばいいじゃん、と。

#Unityからリップシンクに必要なボイスの解析データを取得

今回はユニティちゃんのボイスを解析し、データを取得するためにUnityChanLipSync.csを触ります。
※MMD4M_LipSync.csでも同じように実装できます。

そもそもMMD4Mecanim LipSync Pluginの使い方は[Unity でリアルタイムにリップシンクする MMD4Mecanim LipSync Plugin を作ってみた](http://tips.hecomi.com/entry/20131208/1386514048)と[ユニティちゃんが声に合わせて口パクしてくれるリップシンクアセットを作ってみた](http://tips.hecomi.com/entry/2014/07/21/125319)に掲載されています。
上記記事を見ながら、まず音声解析に必要なデータをセットしてください。

ユニティちゃんの場合、UnityChanLipSync.csを使用すれば大半の設定が完了しています。
エディタでいじったのはMorph SpeedとMax Morph Weightだけです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/a398794d-66ef-4d21-6f95-e4dae4dc12b1.png)

まず、CSVを生成する処理です。

```ruby:UnityChanLipSync.cs
    /**
     * CSVファイル作成
     * 
     * @param txt CSVに書き込む内容
     * @param fileName ファイル名
     */
    void CreateCSV(string txt, string fileName)
    {
        StreamWriter streamWriter;
        FileInfo fileInfo;
        fileInfo = new FileInfo(Application.dataPath + "/Lips" + fileName + ".csv");
        streamWriter = fileInfo.AppendText();
        streamWriter.WriteLine(txt);
        streamWriter.Flush();
        streamWriter.Close();
    }
```

続いてボイス解析後リアルタイムで反映されるモーフターゲットの値を、上記メソッドに渡すようにUpdateMouthメソッドを修正します。

```ruby:UnityChanLipSync.cs
        
	// リップシンク対象モーフターゲット名リスト
 	public string[] morphList = { "MTH_A", "MTH_I", "MTH_U", "MTH_E", "MTH_O" };

	void UpdateMouth(string vowel, float volume)
	{
		for (int i = 0; i < morphNames.Length; ++i) {
			if (vowel == morphNames[i] && volume > minVolume) {
				float weight = volume / normalizedVolume;
				if (weight > maxMorphWeight) {
					weight = maxMorphWeight;
				}
				morphs_[i].morphWeight = weight;
               
			} else {
                morphs_[i].morphWeight *= morphDampingRate;
            }

            // CSV吐き出し
            CreateCSV(playTotalTime + "," + morphs_[i].morphWeight, morphList[i]);
        }

		if (outputter != null) {
			outputter.text = "";
			for (int i = 0; i < morphNames.Length; ++i) {
                outputter.text += "[" + morphNames[i] + "] " + morphs_[i].morphWeight + "\n";
                print("[" + morphNames[i] + "] " + morphs_[i].morphWeight + "\n");
            }
		}
	}
```

これで後はAudio Clipにボイスデータを配置すれば、Playのボタンを押すだけでCSVが出力されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/fece8061-8ee2-e63f-c730-846f3f96c510.png)

こんな感じです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/a9e59f87-a116-0b20-2fc7-cfc7598744b5.png)

中身がちゃんとあれば作成完了です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/5fd15b44-6fff-464f-6aa0-b7ab30300fbc.png)

あとはこの生成したCSVファイルをUE4にFloatCurveで順次インポートします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/8874e77c-86df-8cd7-e0ac-52797f136a3f.png)

全てのCSVデータを読み込みました。あとはこれを実装していくだけです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/104377/3ad90649-e698-81c1-ba7d-fe5bbf63eb93.png)

明日に続きます！

#次回
いよいよラスト！　リップシンク実装と全体のまとめを軽く書きます。