今回のブログはUnityに関する内容です。
Unityと聞くとゲームエンジンというイメージが強いと思いますが、今回はゲームではなくVirtual Reality Model(通称VRM)の表情を制御するための開発に使用します。
今回は表題の通り、VRecというライブラリをパーフェクトシンクに対応させるためにソースコードを修正します。「パーフェクトシンクに対応する必要なんかない!」という方は、正直このブログを見る必要はありません。提供されているライブラリで十分です。
始めに
そもそも「VRMとはなんぞや?」といった方や、本記事内に出てくるBlendShapeやパーフェクトシンクなどの用語の意味が分からない方は以下のサイトを覗いて見てください。
・VRMについて
https://vrm.dev/
・BlendShapeについて
https://vrm.dev/univrm/blendshape/univrm_blendshape.html
(ざっくりですが、VRMの表情を変えるものという認識でOKです)
・パーフェクトシンクについて
https://hinzka.hatenablog.com/entry/2020/08/15/145040
・VRecについて
https://github.com/yutoYokoyama/VRec
(今回修正するコードになります)
・waidayoの導入手順
https://degifeel.com/waidayo-howto/
(iPhoneまたはiPadからUnityへVRMの表情を反映させるために必要なアプリになります)
・waidayoで取得した表情をUnityへ反映させるための手順
https://github.com/nmchan/waidayo/wiki/%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%BF%E3%81%84%E6%96%B9
(別途バーチャルモーションキャプチャー(VMC)とEVMC4Uが必要になります。)
導入時の注意点
今後またVRMに関する依頼が来るかもしれないので、備忘録としてUnityに反映させるまでに詰まった箇所をメモしておきます。皆さんが詰まった場合の手助けになると嬉しいです。
①最後にVMCとUnityを繋ぐ際に、VMCの詳細設定で「OSCでモーション送信を有効にする」と「OSCでモーション受信を有効にする」の両方にチェックを入れてください。
「OSCでモーション送信を有効にする」にチェックを入れることでVMCからUnityへ送信できるようになり、「OSCでモーション受信を有効にする」にチェックを入れることでwaidayoでの動きや表情をVMCに反映することが出来ます。(無料版だと「OSCでモーション受信を有効にする」のチェックボックスはありません)
➁上記のURLでは受信側のポートを39539にしてくださいとありますが、waidayoの送信ポートとVMCの送信ポートが一致していれば問題なく動きます。私の場合、デフォルトではwaidayoとVMCの各ポートは39540でしたがwaidayoの表情の動きがVMCに反映されていることを確認しました。
ただし、VMCの送信ポートと受信ポートは異なる値にしてください。同じポート番号は使用できません。これについてはUnity側のExternalReceiverオブジェクトにアタッチするUOscServerスクリプトのPortとモーション送信のポート番号を合わせてください。私の場合はここのポート番号を39539に設定しました。
③VRec(https://github.com/yutoYokoyama/VRec)に載っている手順の他に、以下のリンクからcsファイルを2つダウンロードしてください。
https://github.com/yutoYokoyama/VRec/tree/master/Extensions
・VRecorderForVRM.cs
・VViewerForVRM.cs
ダウンロードしたらUnityにインポートし、以下の通りにオブジェクトにアタッチしてください。
(オブジェクト名は手順通りに進めた場合の名前を使用しています。変更した方は対応したオブジェクトにアタッチしてください。)
・VRecorderForVRM.cs → VrecRecordオブジェクトにアタッチ
・VViewerForVRM.cs → VRecViewオブジェクトにアタッチ
④今回は表情の反映のみのため、キャリブレーションの実施は不要です。
注意点というほどではありませんが、PC版のwaidayoのアプリを立ち上げなくてもVMCには反映されます。
各種設定と注意点を抑え、Unityにて再生ボタンを押してVMCの動作がUnityのVRMに反映されたら準備完了です。次のステップに進みましょう。
ソースコードの修正
ではまず最初にパーフェクトシンクに対応したソースコードを記載します。
VRecorderForVRMとVViewerForVRMの両方を書き換えてください。
そして書き換え後は必ず上書き保存してください。保存しないとUnity側に反映されません。
一応修正前のコードはコメントとして残していますが、心配な方はオリジナルのファイルのバックアップを取ることをお勧めします。
using System;
using System.Collections.Generic;
using VRM;
using UnityEngine;
namespace VRec.Extensions
{
public class VRecorderForVRM : MonoBehaviour
{
public VRecorderScript VRec;
public BlendShapeAvatar BlendShapeAvatar;
private void Start()
{
if (!VRec)
{
VRec = GetComponent<VRecorderScript>();
}
VRec.AvatarRecordHandler += GetBlendShapes;
}
private VRecEventData[] GetBlendShapes(VRecRecordObj obj)
{
var data = new VRecEventData()
{
ObjectId = obj.Id,
Label = obj.Obj.name,
Type = ObjType.Avatar,
StrData = new string[1] { "BlendShape" },
};
var blends = obj.Obj.GetComponent<VRMBlendShapeProxy>();
if (blends != null)
{
var blendValues = new List<float>();
// デフォルトのBlendShape記録用
/*
foreach (BlendShapePreset key in Enum.GetValues(typeof(BlendShapePreset)))
{
var blendValue = blends.GetValue(BlendShapeKey.CreateFromPreset(key));
blendValues.Add(blendValue);
}
*/
// パーフェクトシンク対応のBlendShape記録用
// BlendShapeClipからBlendShapeKeyを生成
if (BlendShapeAvatar)
{
foreach (BlendShapeClip clip in BlendShapeAvatar.Clips)
{
var blendValue = blends.GetValue(BlendShapeKey.CreateFromClip(clip));
blendValues.Add(blendValue);
}
}
data.FloatValues = blendValues.ToArray();
}
return new VRecEventData[1] { data };
}
}
}
using System;
using VRM;
using UnityEngine;
namespace VRec.Extensions
{
public class VViewerForVRM : MonoBehaviour
{
public VViewerScript VViewer;
public BlendShapeAvatar BlendShapeAvatar;
// Use this for initialization
void Start()
{
if (!VViewer)
{
VViewer = GetComponent<VViewerScript>();
}
VViewer.AvatarDataHandler += BlendShapeSetting;
}
private void BlendShapeSetting(VRecRecordObj obj, VRecEventData data)
{
if (data.StrData[0] == "BlendShape")
{
var blend = obj.Obj.GetComponent<VRMBlendShapeProxy>();
if (!blend)
{
return;
}
var blendNumber = 0;
// デフォルトのBlendShape再生用
/*
foreach (BlendShapePreset key in Enum.GetValues(typeof(BlendShapePreset)))
{
blend.ImmediatelySetValue(BlendShapeKey.CreateFromPreset(key), data.FloatValues[blendNumber]);
blendNumber++;
}
*/
// パーフェクトシンク対応のBlendShape再生用
// BlendShapeClipからBlendShapeKeyを取得
if (BlendShapeAvatar)
{
foreach (BlendShapeClip clip in BlendShapeAvatar.Clips)
{
blend.ImmediatelySetValue(BlendShapeKey.CreateFromClip(clip), data.FloatValues[blendNumber]);
blendNumber++;
}
}
}
}
}
}
書き換え後保存したらUnityにてVrecRecordオブジェクトとVRecViewオブジェクトにアタッチされている上記のソースコードを確認してください。
上手くいっていれば「Blend Shape Avatar」という入力欄が増えているかと思います。
VRMのプレハブにアタッチされているVRM Blend Shape ProxyスクリプトのBlend Shape Avatarに設定されているオブジェクトと同じものをVrecRecordオブジェクトとVRecViewオブジェクトに設定してください。
もし意味が分からない方は以下の画像の③-1をクリックして、

ポップアップで出てきたウィンドウの中からVRMに対応したBlendShapeを選択してください。

もし複数個ある場合は選択すると下にパスが表示されますので、どちらが使用しているVRMのオブジェクトなのか確認してください。
今回は都合上表示の一部を隠しています。ご了承ください。
ここまで設定出来たらVRecの手順に従い表情の録画と再生をしてください。問題なく動作するはずです。
ソースコードの解説
これで終わってもいいのですが、一応ソースコードの解説もしておきます。
全てを解説すると長くなってしまうので、要点だけかいつまんで説明します。
VRecorderForVRM.cs
11行目にてVRMに設定されているBlendShapeのクリップを取得するための変数を用意します。
これがUnity上で新たに追加されていた入力欄の正体です。
35~42行目はコメントとなっていますが、ここがオリジナルのソースコードになります。
本来だとBlendShapePresetという16種類の設定値(A、I、U、E、OやBLINK_L、Unknownなど)を保存しているのですが、このままだとパーフェクトシンクに対応できません。
Unity上で確認すると分かりますが、パーフェクトシンク用に追加されたクリップはすべてUnknownに設定されています。
さらにforeachでループしているため、BlendShapePresetの種類分(16回)しかループしません。もっと言うと、Unknownは1回のみのループとなるためパーフェクトシンクに対応できないということになるわけです。
じゃあどうするか?それが44~53行目のコードになります。
ほぼ形としては同じですが、呼び出している関数が若干違います。
デフォルトのBlendShapePresetが16種類しかないのであれば、すでに登録されているBlendShapeClipから値を取得したらいいじゃないか!その発想に至りました。
47行目ではエラー判定とならないように、BlendShapeAvatarが空の場合は処理を通らないようにしています。
49行目のforeachではBlendShapeに登録されているクリップを順番に呼び出し、そのクリップからBlendShapeKeyを生成、値を保存しています。
51行目はオリジナルのソースコードと同じです。
これで録画についてはパーフェクトシンクに対応できるようになりました。
VViewerForVRM.cs
VRecorderForVRM.cs同様、9行目にてBlendShapeのクリップを取得するための変数を用意します。
31~38行目のコメント部分がオリジナルのソースコードになります。
ここでもVRecorderForVRM.cs同様、foreachにてBlendShapePresetの数だけループで回しています。
同じ説明となるので割愛しますが、このままだと16回のループで終わってしまうためパーフェクトシンクに対応できません。
40~49行目がパーフェクトシンク対応のソースコードになります。
42~44行目はVRecorderForVRM.csと同様なので割愛します。
46行目で第一引数の呼び出し関数をCreateFromClipにすることで登録されているクリップからキーを生成することができ、そのキーをもとに第二引数の値をVRMへ設定し表情を変えている内容となっています。
47行目はオリジナルのソースコードと同様です。
余談
当初は「BlendShapePresetを増やせば対応できるんじゃね?」と考えていましたが、52種類分増やすのが面倒だったのと、汎用性があまりにもなさすぎるので却下となりました。
最後まで動作確認はできていませんが、BlendShapePresetの追加は出来たのでおそらく可能かと思います。おすすめはしませんが。。
まとめ
正直ソースコードの変更量だけみるとたったの数十行ですが、VRMに関する知識が0でしたので2週間弱かかりました。。
最初は依頼主の要望も上手く汲み取れない状況からのスタートでしたのでとても苦労しましたが、依頼主がとても丁寧な方で本当に助かりました。
ソースコードに関する詳細なドキュメントがない中、自分で理解するしかなかったので徐々にゴールが見えてきた時の興奮と、実際に動いた時の達成感は半端なかったです。今のところ今年一の達成感でした。これだからプログラムはやめられないんですよね。
私の感想となってしまいましたが、このブログが一人でも多くのVTuberの方の役に立てればと思います。
もし本記事に関することで不明点がありましたらお問い合わせフォームより連絡いただけますと回答いたします。
また、関連する案件を依頼したい場合はココナラサイトからご相談ください。
サービス購入前にDMにて依頼したい内容をお送りいただいても大丈夫です。
ご連絡お待ちしております。
https://coconala.com/users/2764353
次回のブログの内容は決まっておりません。(多分改めて自己紹介を書くことになるかも?)
以上となります。最後までご覧いただきありがとうございました。
コメント