Meta Quest2で左右に別々の映像を表示する方法
この記事で伝えたいこと
Meta Quest 2で左右の目に別々の画像を映す方法
環境
Unity 2021.3.4f1
Meta Quest 2
手法
0.XR Interaction Toolkitを使うべきか
XR Interaction Toolkitについて、2022年4月時点では、各ヘッドセット固有の機能(たとえばQuestのハンドトラッキング)が使用できない、モーションコントローラーのグラフィックスを表示できない、パフォーマンス懸念があるといったデメリットがあります。詳細は下記を参照してください。
Unity VR開発メモ(XR Interaction Toolkit + OpenXR Plugin) - フレームシンセシス
1.Unity でプロジェクトを作成する
Unityの新規プロジェクトを「3D」テンプレートで作成する。
2.XR Interaction Toolkitをインストールする
1.Window > Package Managerを開きます。
2.左上の「+」からAdd package by nameで「com.unity.xr.interaction.toolkit」と入力し、インストールします。
3.XR Interaction Toolkit>Sample>Starter Assetsを「import」を押してインポートする。
3.シーンにカメラリグを作成する
次にシーンにカメラリグを作りますが、それに先立って、Preset Managerという機能を使用して、カメラリグのコントローラーの入力設定が自動的に行われるようにします。
1.Edit>Project Settings>Preset Managerを開く。
2.Add Default PresetでXR Controller (Action-based)を選択します。
3.“Left”、“Right"という文字列にXRI Default Left ControllerとXRI Default Right Controllerを割り当ててください。
4.Input Actionを有効
1.シーンに空のゲームオブジェクトを作成。
2.Component>Input>Input Action Managerをアタッチ。
3.XRI Default Input Actionsを設定。
5.OpenXR Pluginのセットアップ
1.Edit > Project Settings > XR Plugin Management を開いて「Install XR Plugin Management」ボタンを押す。
2.Plug-in Provides が表示されたら 「OpenXR」 にチェックを入れる。
6.XR Originの追加
Main CameraをXR Origin(VRカメラ)に変更します。
1.GameObject>XR>XR Originを選択する。
2.XR Originの子にMain Cameraがあることを確認する。
7.Camera設定&Plane追加
2つのカメラを用意して、Culling Maskという特定のオブジェクト(Plane)のみを映す機能を使って別々に映す。
1.Inspector>Layer>Add Layerで「Left Eye」と「Right Eye」を追加。Layerは何番でもよい。
2.Main CameraのCamera>Culling Maskを「Left Eye」のみに設定する。(Main cameraの名前もLeft Cameraと改名。)
3.Camera>Target Eyeを「Left」に設定する。
4.Left Cameraの子にPlane(GameObject>3D Object>Plane)を追加。座標はすべて同じにする。
5.PlaneのLayerを「Left Eye」に設定。
6.Left Cameraを複製して、右目用を作る。
7.右目用のカメラのCulling Maskを「Right Eye」のみ、Target Eyeを「Right」、PlaneのLayerを「Right Eye」にする。
8.Materialと画像の追加
1.Material(project window>右クリック>Create>Material)を2つ作成。(ここではMaterial01,Material02)
2.MaterialのShaderを両方ともDiffuseにする。(Shader>Legacy Shaders>Diffuse)
3.画像をプロジェクトに追加し、Texture Typeを「Sprite(2D and UI)」に変更。(ここではImage01,Image02)
4.それぞれのMaterialの「None(Texture)」部分に画像をドラッグ&ドロップ。(ここではMaterial01←Image01、Material02←Image02)
5.それぞれのMaterialをHierarchyのPlaneにドラッグ&ドロップ。(ここではLeft Plane←Material01、Right Plane←Material02)
【Unreal C++】⑨モジュール 【UE4】
C++でコードを書く際、UE4の公式ドキュメントで調べていると思いますが、UnrealC++ではHeaderファイルをincludeするだけではだめみたいです。
以下によるとUE4のC++コードは、「モジュール」と呼ばれる単位で、「UnrealBuildTool」を通してビルドされるそうです。なので必要に応じて追加してあげる必要があります。
[UE4] モジュールについて|株式会社ヒストリア
モジュールに関してUE4.15以降で仕様変更があったので以下を参照してください。
ちなみに自分の環境はver4.18.3です。
miyahuji111.hatenablog.com
追加方法
①[ProjectName].Build.cs
([ProjectFolder]/source/[ProjectName]/)にある[ProjectName].Build.csを開くと、以下のようになっていると思います。
using UnrealBuildTool; public class hogeProject : ModuleRules { public hogeProject(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); PrivateDependencyModuleNames.AddRange(new string[] { }); } }
②Moduleの追加
公式ドキュメントのAPIのリファレンスを見るとModuleの項目があり、
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG","UMGEditor" });
のnew string[] 以下の部分になければ "" で囲んで追加することでその関数を使用することが出来ます。
【Unreal C++】 ⑧Level Streaming 【UE4】
C++でLevel Streaming 使うときにちょっと躓いたのでメモ。
BPでのLevel Streamingは以下を参考に。
unrealengine.hatenablog.com
詳しくは以下に書いていますがロード後にすぐ表示しているので、今回はロード後すぐに表示しないパターンを書いておきます。
docs.unrealengine.com
ロード
ロード後すぐに表示しないパターンにするため
第3引数・・・読み込み後に表示を行うかどうか
第4引数・・・同期読み込みを行うかどうか
を両者ともfalseにしました。
docs.unrealengine.com
[UE4] レベルストリーミングについて|株式会社ヒストリア
マップ(サブレベル)表示
BPと大体同じで
第1引数・・・表示非表示をいじりたいマップ
第2引数・・・表示非表示のフラグ
UFunctionLibrary::beVisibleMap(MapName, true);
アンロード
関数については以下を参考に。
docs.unrealengine.com
マップ(サブレベル)非表示
UFunctionLibrary::beVisibleMap(MapName, false);
Tips
マップの遷移で分岐があったので2つのマップを同時にロードする必要があり、FLatentActionInfo型の変数を2つ用意するだけでいいのかなと思ったのですが、1つだけしかロードされませんでした。
調べてみると、UUIDで識別しているみたいで2つの変数に別々のUUIDを与えてあげることで2つロードすることができました。
ちなみにUUIDのデフォルトの値は-1みたいです。
【Unreal C++】⑦イベントディスパッチャー【UE4】
今回はイベントディスパッチャーについてです。
BPでの実装は以下を参考に。
unrealengine.hatenablog.com
動的マルチキャストデリケートでBP側のイベントディスパッチャーと同等の処理が出来るみたいです。
呼び出し側
DECLARE_DYNAMIC_MULTICAST_DELEGATEマクロを用いてクラスを作成します。
命名規則からクラス名には「F」の接頭辞が必要です。
また宣言の際にプロパティをBlueprintAssignableにする必要があります。
Call.h
#include "CoreMinimal.h" #include "Call.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE(FhogeDispather); UCLASS() class HogeProject_API ACall : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties ACall(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; //Event Dispather(Multicast Delegate) UPROPERTY(BlueprintAssignable) FhogeDispather hogeDispather; }
先ほど作成したクラスの変数のBroadcastメソッドを呼んであげます。
Call.cpp
#include "Call.h" ACall::ACall() { } ACall::HogeFunc() { hogeDispather.Broadcast(); }
受信側
Funcは呼び出したい処理です。
UFUNCTION()がついてないとクラッシュしました。
Receive.h
#include "CoreMinimal.h" #include "Call.h" #include "Engine.h" //GEngine #include "Receive.generated.h" UCLASS() class HogeProject_API AReceive : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AReceive(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; //Event Dispather(Multicast Delegate) UFUNCTION() void Func(); }
BeginPlayでAddDynamicを用いて作成したクラスの変数と呼びたい処理をバインディングします。
Receive.cpp
#include "Receive.h" // Sets default values AReceive::AReceive() { } // Called when the game starts or when spawned void AReceive::BeginPlay() { Super::BeginPlay(); //Event Dispather for (TActorIterator<ACall>ActItr(GEngine->GameViewport->GetWorld()); ActItr; ++ActItr) { ACall* Call = *ActItr; Call->hogeDispather.AddDynamic(this, &AReceive::Func); } } void AReceive::Func() { //呼び出したい処理 }
以上で呼び出し(call)側のHogefunc関数が呼ばれるタイミングで受信(Receive)側のFunc関数が同時に呼ばれます。
【Unreal C++】⑥Interface【UE4】
今回はC++とブループリントでインターフェースを呼ぶ機会があったので備忘録として。
他にもいい方法があると思うのであくまで参考程度でお願いします。
エンジンはVer4.18.3を使用しています。
ちなみにブループリントインターフェースなどインターフェースについては以下のリンクを参照してください。
docs.unrealengine.com
unrealengine.hatenablog.com
①クラスの作成&宣言
おそらくBP側で作成したインターフェースはC++側に呼ぶことが出来ないのでC++側でインターフェースを作成します。C++でインターフェースを継承したクラスを作成します。
関数指定子をBlueprintImplementableEventでもいけるという情報もありましたが、参考したサイトの多くがBlueprintNativeEventを使用していたのでこちらを使用します。
unreallife.hatenablog.com
hogeInterface.h
#include "CoreMinimal.h" #include "hogeInterface.generated.h" UINTERFACE(Blueprintable) class hogeProject_API UhogeInterface : public UInterface { GENERATED_UINTERFACE_BODY() }; class hogeProject_API IhogeInterface { GENERATED_IINTERFACE_BODY() public: UFUNCTION(BlueprintNativeEvent, BlueprintCallable) void hogeFunc(); }
hogeInterface.cpp
#include "hogeInterface.h" UhogeInterface::UhogeInterface(const FObjectInitializer& ObjectInitializer) :Super(ObjectInitializer) { } void IhogeInterface::hogeFunc_Implementation() { }
ブループリントでの実装
イベント側
「Event hogeFunc」を呼んであげます。
イベントを呼び出すためには「Class Settings→interface→Implemented Interface」に先ほど作成したhogeInterfaceを追加してコンパイルする必要があります。
呼び出し側
「hogeFunc(Message)」を呼んであげます。
Targetノードにはイテレータ、Get All Actors with Interface、Get All Widgets with Interfaceなどを用いて検索したイベント側のクラスを繋いであげます。
自分はレベルストリーミング使用しているのでGet All Widgets of Classの第2引数のTop Level Onlyはfalseにしました。
C++での実装
イベント側(hogeActor)
hogeInterface を継承したクラスを作成し、関数をOverrideしてあげます。
hogeActor.h
#include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "hogeInterface.h” #include "hogeActor.generated.h" UCLASS() class hogeProject_API AApple : public AActor, public IhogeInterface { GENERATED_BODY() public: //Interface virtual void hogeFunc_Implementation() override; };
hogeActor.cpp
void hogeActor::hogeFunc_Implementation() { //処理 }
呼び出し側
検索して配列に格納してRanged-forを用いて配列に対して処理をしてあげます。
TArray<AhogeActor*>hogeActAry;
UWorld* World=GEngine->GameViewport->GetWorld();
UGameplayStatics::GetAllActorsWithInterface(World,UhogeInterface::StaticClass(),hogeActAry);
for(AhogeActor* hogeAct : hogeActAry)
{
IhogeInterface::Execute_hogeFunc(hogeAct);
}
Get All Actors of Class、Get All Widgets of Classなどは遅い処理なのでTickなどで呼ばないでください。
docs.unrealengine.com