UnityとUE4モーションデータを出力してみた(VMCtoMOP)

VMCtoMOPでVMC(Unity)とMOP(UE4)両方のモーションデータを出力してみました。

概要

前回の記事ThreeDPoseTracker(Unity) -> VMCtoMOP(Unity) -> MopUE4という形でUnityツールの骨格認識経由でUE4のモデルを動かしましたが、リリース版のものを使ったので、中身はどういう感じか分かりませんでした。

モーション周りの勉強のため、プロジェクト上でモーションデータ取り出したいと思います。最新の ThreeDPoseTracker(Unity) は現時点 Close Source になっているらしいですが、VMCtoMOPOpen Sourceなので、VMCtoMOPのプロジェクトを立ち上げ、ソース改造し、モーション(VMCとMOP両方)を出力してみたいと思います。

マシン環境

CPU : Intel i7-6920HQ
GPU : AMD Radeon Pro 460
Memory : 16GB
System : Windows 10 (with .NET 5 installed)
IDE : Visual Studio 2019 Community
Editor : Visual Studio Code
Terminal : Windows Terminal (or Command Prompt)
Engine : Unity 2020.3.17f1 Personal

事前準備

GitHub: VMCtoMOP by HAL9HARUKU まず上記のプロジェクトをダウンロードします(zipファイルgit cloneかどちでも大丈夫)、。展開(Clone)したものをUnity Hubで開いて、Safe Modeを無視し、Ignoreを押してプロジェクトを開きます。

VMCtoMOP 依存ライブラリなどを参照することで、下記のパッケージも用意します: - TextMeshPro 3.0.4 (Unity 公式) - UniVRM v0.66.0 - uOSC v0.0.2 - UniRx Ver 7.1.0 - UniTask Ver.2.2.5 - VRoid: Vivi

Unity Packages

Assets -> Import Package -> Custom Packageでダウンロードした四つパッケージをプロジェクトに追加します。

TextMeshPro

TextMeshProは公式のPackage ManagerInstallできます。

 

それからMainSceneを開くと、TMP Importerのことが聞かれます。Import TMP Essentialsを選択してください。

VRoid Vivi

版権などの問題だと思いますが、キャラクターモデルViviはプロジェクトに含まれていません。外部リンクVRoid: ViviViviのVRMファイルをダウンロードします(サイトのアカウント登録が必要)。

 

先ほどUniVRMが追加されたので、メニューにVRM0ボタンが表示されます。VRM0 -> ImportでダウンロードしたViviのVRMファイルをプロジェクトに追加します。

 

もともとシーンにあったViviを削除して、追加されたViviをシーンに入れて、新しいViviManager -> Manager(Script) -> AnimatorManager -> Mop Sender(Script) -> Animator の参照にします(Drag&Drop)。

シーンにあるViviを選択し、InspectorのとこでVRM Meta(Script) -> Information -> Versionにワーニングが出ていて、適当な数字を入れれば大丈夫です。

Viviのポジションを(0,0,0)にしてください。

ソースの改造

.jsonの形で高速、大量なデータの書き込みは問題が発生してますので(解決したら修正します)、.txtの形でモーションデータを保存します。

モーションが発生するタイミングはTime.deltaTimeで加算し、記録されます。

VMCモーションデータ(for Unity)の出力

まずOscServer.csにソースコードを追加し、VMCモーションデータをアウトプットします。

using System.IO;

public void Run(int port)
{
    // ......省略     

    CreateFileAndWriter();
}

private void Update()
{
    // lock(lockObject_)
    // {
        // while (parser_.messageCount > 0)
        // {
            // var message = parser_.Dequeue();
            // onDataReceived.Invoke(message);

            OutputVmcMotion(message);
        // }
    // }

    time += Time.deltaTime;
}

FileStream fileStream = null;
StreamWriter writer = null;
float time = .0f;

private void CreateFileAndWriter()
{
    var dirInfo = Directory.CreateDirectory("D:/MotionData/");
    string fileName = "MotionVmc" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt";
    fileStream = File.Create(dirInfo.FullName + fileName);
    writer = new StreamWriter(fileStream, System.Text.Encoding.UTF8);
}

private void OutputVmcMotion(Message message)
{
    if(message.address == "/VMC/Ext/Root/Pos" || message.address == "/VMC/Ext/Bone/Pos")
    {
        string address = message.address;
        string name = (string)message.values[0];
        Vector3 pos = new Vector3((float)message.values[1], (float)message.values[2], (float)message.values[3]);
        Quaternion rot = new Quaternion((float)message.values[4], (float)message.values[5], (float)message.values[6], (float)message.values[7]);

        writer.WriteLine($"a:{address}, n:{name}, p:{pos}, r:{rot}, t:{time}");
    }
}

private void OnDestroy()
{
    writer?.Close();
}

CreateFileAndWriter()で現時点をファイル名(txt)としてD:/MotionData/VMCモーションデータを保存するファイルを作って、ファイルに書き込むStreamWriterを準備します。

OutputVmcMotion(Message message)でVMCのモーションデータをa:{address}, n:{name}, p:{pos}, r:{rot}, t:{time}の形で作ったファイルに保存します。

ファイルに書き込むことが終ったら(プログラムが中断されたら)OnDestroy()StreamWriter.Close()でファイルを解放してください。

MOPモーションデータ(for UE4)の出力

Unreal Engine用のMOPモーションデータを保存するため、MopSender.csを改造します。

MopSender.cs

using System;
using System.IO;

private void Start()
{
    //......省略

    CreateFileAndWriter();
}

private void OnDestroy()
{
    //......省略

    writer?.Close();
}

private void SendMotion()
{
    //......省略

    time += Time.deltaTime;
}

private void PostBodySize()
{
    for (var parameterIndex = 0; parameterIndex < BoneParameterNum; ++parameterIndex)
    {
        //......省略

        OutputMopBodySize(message);
    }
}

private void PostBones()
{
    // var sb = new System.Text.StringBuilder();

    for (var parameterIndex = 0; parameterIndex < BoneParameterNum; ++parameterIndex)
    {
        //......省略

        uOSC.Message message = new uOSC.Message($"/Mop/BoneControl/{BoneNameList[parameterIndex]}",
            -location.x * 100.0f, location.z * 100.0f, location.y * 100.0f,
            -rotation.x, rotation.z, rotation.y, rotation.w);

        this.sendBundle.Add(message);

        OutputMopBone(message);

    }

}

FileStream fileStream = null;
StreamWriter writer = null;
float time = .0f;

private void CreateFileAndWriter()
{
    var dirInfo = Directory.CreateDirectory("D:/MotionData/");
    string fileName = "MotionMop" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt";
    fileStream = File.Create(dirInfo.FullName + fileName);
    writer = new StreamWriter(fileStream, System.Text.Encoding.UTF8);
}

private void OutputMopBodySize(uOSC.Message message)
{
    string name = message.address;
    Vector3 size = new Vector3(
        Convert.ToSingle(message.values[0]),
        Convert.ToSingle(message.values[1]),
        Convert.ToSingle(message.values[2]));

    string str = $"n:{name}, s:{size}, t:{time}";
    writer.WriteLine(str);
}

private void OutputMopBone(uOSC.Message message)
{
    string name = message.address;
    Vector3 pos = new Vector3(
        Convert.ToSingle(message.values[0]),
        Convert.ToSingle(message.values[1]),
        Convert.ToSingle(message.values[2]));

    Quaternion rot = new Quaternion(
        Convert.ToSingle(message.values[3]),
        Convert.ToSingle(message.values[4]),
        Convert.ToSingle(message.values[5]),
        Convert.ToSingle(message.values[6]));

    string str = $"n:{name}, p:{pos}, r:{rot}, t:{time}";
    writer.WriteLine(str);
}

CreateFileAndWriter()で現時点をファイル名(txt)としてD:/MotionData/MOPモーションデータを保存するファイルを作って、ファイルに書き込むStreamWriterを準備します。

Unreal Engineに転送するMOPモーションデータBody Size(size)とBone Data(position, rotation)二種類があるらしいので、OutputMopBodySize(uOSC.Message message)OutputMopBone(uOSC.Message message) を用いいします。VMCの場合はaddressnameに分けていますが、MOPは骨パスはnameにまとめているらしいです。

Body Sizen:{name}, s:{size}, t:{time}Bone Datan:{name}, p:{pos}, r:{rot}, t:{time}という感じにフォマードし、D:/MotionData/に記録されます。

プロジェクト実行

前回の記事の流れて(モーションデータの記録だけなので、UE4の部分は起動しなくても大丈夫)、VMCtoMOPリリース版の代わりに、今回作ったUnityプロジェクトを実行します。

 

実行されたら、スクリーンショットの感じでVMCMOP両方のモーションデータが保存されるはずです。

最後

モーションデータを保存できて、画面上Key Annotationの描画やモーションの機械学習などができるかもしれないと思います。ただ、実際使いたいモーションデータのフォマードに合わせて一部追加改造が必要です。

自分がモーションについて知識が少ないので、またVMCMOPについて詳しく調べたいと思います。

以上です。