OVRLipSyncのセットアップ
以下の記事を参照して OVRLipSync を利用できるようにします。
ダウンロード と セットアップ の項目を行ってください。
ダウンロード と セットアップ の項目を行ってください。
Unity でリップシンクができる OVRLipSync を試してみた
(モデルは下記のVRMモデルを使うので、適宜読み替えてください)
モデルについて
元記事ではユニティちゃんを利用していますが、今回は VRM を利用します。(VRMモジュールがインストールされてない場合はVRMのパッケージをダウンロードしてインポートしてください)
VRoidStudioのサンプルモデルをダウンロードするか、VRoidStudio製のモデルを用意してインポートしてください。
上記サンプルモデルモデルの場合はスキンドメッシュレンダラーに Face をセットして Viseme To Blend Targets の値には以下のように指定します。
他のモデルを利用する場合は スキンドメッシュレンダラー に あいうえお のブレンドシェイプを含むメッシュを指定し、 Viseme To Blend Targets の 10 に "あ" , 11 に "え ", 12 に "い" , 13 に "お" , 14 に "う" のブレンドシェイプ番号を指定します(詳しくはこちらの記事を参照してください)。
音声を用意
リップシンク用の音声を用意します。伴奏などが含まれているとうまくいかないので、ボーカルのみのデータを用意します。最近だとRVCの伴奏ボーカル分離がいい感じです。ボーカルのみのデータができたら OVRLipSyncContext をアタッチしたときに生成される Audio Source のオーディオクリップにセットしておきます。
録画スクリプト
以下のスクリプトを作成し、適当なGameobjectにアタッチします。using UnityEngine;
using System.Collections.Generic;
public class BlendShapeRecorder : MonoBehaviour
{
public SkinnedMeshRenderer skinnedMeshRenderer;
public float recordDuration = 10f;
private float timer = 0f;
private bool isRecording = false;
private Dictionary<int, AnimationCurve> curves = new Dictionary<int, AnimationCurve>();
private Mesh mesh;
void Start()
{
mesh = skinnedMeshRenderer.sharedMesh;
for (int i = 0; i < mesh.blendShapeCount; i++)
{
curves[i] = new AnimationCurve();
}
isRecording = true;
}
void Update()
{
if (!isRecording) return;
timer += Time.deltaTime;
for (int i = 0; i < mesh.blendShapeCount; i++)
{
float weight = skinnedMeshRenderer.GetBlendShapeWeight(i);
curves[i].AddKey(timer, weight);
}
if (timer >= recordDuration)
{
isRecording = false;
SaveAnimationClip();
}
}
void SaveAnimationClip()
{
AnimationClip clip = new AnimationClip();
foreach (var kvp in curves)
{
string path = "blendShape." + mesh.GetBlendShapeName(kvp.Key);
clip.SetCurve("", typeof(SkinnedMeshRenderer), path, kvp.Value);
}
#if UNITY_EDITOR
UnityEditor.AssetDatabase.CreateAsset(clip, "Assets/RecordedBlendShape.anim");
UnityEditor.AssetDatabase.SaveAssets();
Debug.Log("BlendShapeアニメーションを保存しました");
#endif
}
}
Play モードにすると Asset フォルダに RecordedBlendShape.anim が生成されます。 sil に対応するブレンドシェイプはセットしてないので、(このモデルの場合は) Fcl_ALL_Nuetral に割当てられています。使ってない他のブレンドシェイプも含めて削除しておきましょう。
VR Dance Viewer の VRM Animation Brige データを作る
ついでに、VR Dance Viewer 用のリップシンクデータも作ってみます。VrmAnimationBridge を操作する事前準備を済ませておいてください。
先ほどまでの OVRLipSync の設定までを終えたモデルに VrmAnimationBridge をアタッチします。
以下のコードを作成し、適当な Gameobject にアタッチします。
(コードはVRoidのモデル用です。他のモデルで使う場合は Fcl_MTH_x のブレンドシェイプ名を使うモデルのものに書き換えてください)
using UnityEngine;
using System.Collections.Generic;
using System.Reflection;
using VRM.AnimationBridgeSample;
public class LipSyncRuntimeRecorder : MonoBehaviour
{
public SkinnedMeshRenderer skinnedMeshRenderer;
public OVRLipSyncContext lipSyncContext;
public AnimationBridge animationBridge;
public float recordDuration = 10f;
private Dictionary<string, AnimationCurve> blendShapeCurves = new Dictionary<string, AnimationCurve>();
private Dictionary<string, AnimationCurve> bridgeCurves = new Dictionary<string, AnimationCurve>();
private float timer = 0f;
private bool isRecording = false;
private readonly Dictionary<int, (string blendShapeName, string bridgeField)> visemeMap = new Dictionary<int, (string, string)> {
{ 0, ("Fcl_MTH_A", "Lip_A") },
{ 1, ("Fcl_MTH_I", "Lip_I") },
{ 2, ("Fcl_MTH_U", "Lip_U") },
{ 3, ("Fcl_MTH_E", "Lip_E") },
{ 4, ("Fcl_MTH_O", "Lip_O") }
};
void Start()
{
foreach (var kvp in visemeMap)
{
blendShapeCurves[kvp.Value.blendShapeName] = new AnimationCurve();
bridgeCurves[kvp.Value.bridgeField] = new AnimationCurve();
}
timer = 0f;
isRecording = true;
}
void Update()
{
if (!isRecording) return;
timer += Time.deltaTime;
var frame = lipSyncContext.GetCurrentPhonemeFrame();
foreach (var kvp in visemeMap)
{
int visemeIndex = kvp.Key;
string blendShapeName = kvp.Value.blendShapeName;
string bridgeField = kvp.Value.bridgeField;
float visemeValue = frame.Visemes[visemeIndex];
float blendValue = visemeValue * 100f;
int blendIndex = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(blendShapeName);
if (blendIndex >= 0)
skinnedMeshRenderer.SetBlendShapeWeight(blendIndex, blendValue);
blendShapeCurves[blendShapeName].AddKey(timer, blendValue);
FieldInfo field = animationBridge.GetType().GetField(bridgeField);
if (field != null)
field.SetValue(animationBridge, visemeValue);
bridgeCurves[bridgeField].AddKey(timer, visemeValue);
}
if (timer >= recordDuration)
{
isRecording = false;
SaveAnimationClip();
}
}
void SaveAnimationClip()
{
AnimationClip clip = new AnimationClip();
foreach (var kvp in blendShapeCurves)
{
string path = "blendShape." + kvp.Key;
clip.SetCurve("", typeof(SkinnedMeshRenderer), path, kvp.Value);
}
foreach (var kvp in bridgeCurves)
{
clip.SetCurve("", typeof(AnimationBridge), kvp.Key, kvp.Value);
}
#if UNITY_EDITOR
UnityEditor.AssetDatabase.CreateAsset(clip, "Assets/RecordedBlendShape.anim");
UnityEditor.AssetDatabase.SaveAssets();
Debug.Log("完了:Assets/RecordedBlendShape.anim に保存されました");
#else
Debug.LogWarning("AnimationClipの保存はEditorでのみ可能です");
#endif
}
}
Play モードにすると Asset フォルダに RecordedBlendShape.anim が生成されます。
VR Dance Viewer 用のタイムラインを作る際、今回作ったトラックをモーションのオーバーライドとして追加すると、リップシンクも動くデータになります。
0 件のコメント:
コメントを投稿