update : 2024-10-03
2024-10-02 / @syui

vrm , ue / ue

ue5.5と最新のvmc事情

unreal engine 5.5.0 preview(ue5.5p)がインストールできるようになっています。今回は最新環境のvmc事情を解説します。

vmcとは

webカメラから表情や動きをキャラクターに反映させるためのものです。vmcはprotocolとclientがあります。大抵はprotocolを指します。webカメラからの読み取りをcaptureといいます。つまり、captureとprotocolとclientを組みわせて動作します。ueで使うには更にvmcを受信してキャラクターに反映させるpluginが必要です。わけがわからないと思いますが、そんな感じです。

vmc4ueをue5.5でbuildしてみよう

この手順は興味がある人以外は読み飛ばすことを推奨します。表情を動かしたい人はこの方法で問題を解決することができません。

vmc4ueはue5.1までしかbuildされていません。そこでue5.5でbuildして使えるようにしてみます。

まずc++でprojectを作成し、patchをsrcに当てて$project/Plugins/に入れます。projectをueで開くとbuildされます。正常に終了するとeditorが開きます。

c++で作成したprojectには$project.slnが作成されますので、それを開いてrebuildしてもいいです。

なお、patchはbuildが通るよう適当に作ったものです。vmcは動きますが、表情は動きませんでした。

--- ./VMC4UE/VMC4UE/Source/VMC4UE/Source/VMC4UEBlueprintFunctionLibrary.cpp
+++ ./VMC4UEBlueprintFunctionLibrary.cpp
@@ -119,27 +119,29 @@ UVMC4UEStreamingSkeletalMeshTransform* UVMC4UEBlueprin
 	{
 		return nullptr;
 	}
-	
+
+	UVMC4UEStreamingSkeletalMeshTransform* StreamingSkeletalMeshTransform = nullptr;
+
+	// Try to get existing transform
 	{
-		// Get
 		FRWScopeLock RWScopeLock(OSCManager->RWLock, FRWScopeLockType::SLT_ReadOnly);
-		auto StreamingSkeletalMeshTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
-		if (StreamingSkeletalMeshTransform != nullptr)
+		auto FoundTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
+		if (FoundTransform != nullptr)
 		{
-			return *StreamingSkeletalMeshTransform;
+			return *FoundTransform;
 		}
 	}
+
+	// Create new transform if not found
 	{
-		// Create
 		FRWScopeLock RWScopeLock(OSCManager->RWLock, FRWScopeLockType::SLT_Write);
-		auto StreamingSkeletalMeshTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
-		if (StreamingSkeletalMeshTransform != nullptr)
+		auto FoundTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
+		if (FoundTransform != nullptr)
 		{
-			return *StreamingSkeletalMeshTransform;
+			return *FoundTransform;
 		}
-		UVMC4UEStreamingSkeletalMeshTransform* NewStreamingSkeletalMeshTransform = NewObject<UVMC4UEStreamingSkeletalMeshTransform>();
 
-		//FRWScopeLock RWScopeLock2(NewStreamingSkeletalMeshTransform->RWLock, FRWScopeLockType::SLT_Write);
+		UVMC4UEStreamingSkeletalMeshTransform* NewStreamingSkeletalMeshTransform = NewObject<UVMC4UEStreamingSkeletalMeshTransform>();
 		OSCManager->StreamingSkeletalMeshTransformMap.Emplace(Port, NewStreamingSkeletalMeshTransform);
 
 		// Bind Port
@@ -149,9 +151,10 @@ UVMC4UEStreamingSkeletalMeshTransform* UVMC4UEBlueprin
 
 		OSCManager->OscReceivers.Emplace(OscReceiver);
 
-		return NewStreamingSkeletalMeshTransform;
+		StreamingSkeletalMeshTransform = NewStreamingSkeletalMeshTransform;
 	}
-	return nullptr;
+
+	return StreamingSkeletalMeshTransform;
 }
 
 void UVMC4UEBlueprintFunctionLibrary::RefreshConnection(float Seconds)
--- ./VMC4UE/Source/VMC4UEEd/Source/VMC4UEBoneMappingAssetFactory.cpp
+++ ./VMC4UEBoneMappingAssetFactory.cpp
@@ -5,6 +5,8 @@
 #include "../../VMC4UE/Include/VMC4UEStreamingData.h"
 #include "Dom/JsonObject.h"
 #include "JsonObjectConverter.h"
+#include "UObject/ConstructorHelpers.h"
+#include "UObject/UObjectGlobals.h"
 
 UVMC4UEBoneMappingAssetFactory::UVMC4UEBoneMappingAssetFactory(const FObjectInitializer &ObjectInitializer)
     : Super(ObjectInitializer)
@@ -26,11 +28,12 @@
     return UVMC4UEVRMMapping::StaticClass();
 }
 
+
 UObject *UVMC4UEBoneMappingAssetFactory::FactoryCreateText(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, UObject *Context, const TCHAR *Type, const TCHAR *&Buffer, const TCHAR *BuferEnd, FFeedbackContext *Warn)
 {
     FString TextData = FString(Buffer);
 
-    UVMC4UEVRMMapping *NewAsset = CastChecked<UVMC4UEVRMMapping>(StaticConstructObject_Internal(InClass, InParent, InName, Flags));
+    UVMC4UEVRMMapping* NewAsset = NewObject<UVMC4UEVRMMapping>(InParent, InClass, InName, Flags);
     if (!IsValid(NewAsset))
     {
         return nullptr;
$ git clone https://github.com/HAL9HARUKU/VMC4UE

$ patch -u ./VMC4UE/VMC4UE/Source/VMC4UE/Source/VMC4UEBlueprintFunctionLibrary.cpp < VMC4UEBlueprintFunctionLibrary.cpp.patch
$ patch -u ./VMC4UE/VMC4UE/Source/VMC4UEEd/Source/VMC4UEBoneMappingAssetFactory.cpp < VMC4UEBoneMappingAssetFactory.cpp.patch

vrm4uを5.5向けにbuildしてみる

この手順は興味がある人以外は読み飛ばすことを推奨します。表情を動かしたい人はこの方法で問題を解決することができません。

vrm4uは既に5.5のbuildをreleasesしています。

ただいくつかの処理が5,4,0向けのようです。それを書き直してbuildしてみます。

$ git clone https://github.com/ruyo/VRM4U
$ cd VRM4U
$ git reset --hard a261860872936c8654e1705a91cff6f8224dbee5
$ grep -R 5,4,0 ./Source/*|cut -d : -f 1|xargs sed -i '' 's/5,4,0/5,5,0/g'
$ grep -R 5,5,0 .

これを先程と同じ手順でue5.5で開いてbuildします。

vrm4u(vmc)はbuild後にも表情を動かせるのだろうか

わかりません。私の環境下では動きませんでした。他の人も動かない可能性がありますが、issueを読む限り動くようにも思えます。

この問題はvrm4u 20241007で修正されました

issue : https://git.syui.ai/ai/ue/issues/9

それではどうするのか。livelink(face)を使います。ここからは少しめんどくさいことになりますが、かなり多くのアプリが必要です。

  • 動き : webcam motion capture(vmc送信) + vseeface(vmc受信/送信) + vrm4u(vmc受信)
  • 表情 : iphone + livelink face

まず、vrm4uwebcam motion captureのvmcを直接受信できません。なので、一旦、vseefaceなどのclientで受信する必要があります。それをvrm4uで受信するportに送信します。なお、vmc4ueでは直接受信できて動きます。また、xr-animator, vseeface, vmc(client)も受信できて動いています。不思議な現象です。

vrm4u(vmc)はbuild後は表情が動かないので、表情はlivelinkを使用します。これはiphoneにlivelink faceというappがあります。ueでいくつかのpluginを有効にします。

  • live link
  • apple arkit
  • apple arkit face support

/VRM4U/Util/Actor/latest/BP_LiveLinkFaceをmapにおいて、live link subject -> iphone, target actor sk -> SK_$nameを設定します。

これでbuildすると表情を動かすことができます。

character(player)にlivelinkを当てるには

character(player)にlivelinkを当てるにはblueprintを編集する必要があります。characterのdirにでもBP_LiveLinkFaceをcopyしてBP_Player(CBP_Character)に追加します。

ここでBP_LiveLinkFace -> livelink subject -> iphoneをセットしておきます。

次にBP_LiveLinkFaceを以下のような形でsk_$nameに置き換えます。場所はコメントを参考にしてください。私の場合は見た目をカスタマイズしているので少し複雑です。

https://blueprintue.com/blueprint/pu_xl52s/

vmcで行くべきか

基本的には依存関係が少なく使うアプリが少ないほうがいいですね。vmcの更新頻度やclient, vrm1のsupport状況を見るとueはlivelink路線のほうがいいかもしれません。

仮にwebcam motion capture -> vrm4uで表情も体も動かせるならvmcで問題ないですが、表情にlivelinkを使うなら全部統一するのがいいですね。

mocopiicloneはlivelinkなのでそちらが使えると思います。