実装例
OpenXR 拡張を薄くラップする例。
詳しくはソース参照のこと。
openxr 拡張 | 実装されているデバイス | 備考 |
---|---|---|
XR_EXT_hand_tracking | quest, quest2, quest pro, hololens2, HTC Vive Cosmos, HTC Vive Focus 3, Varjo など | quest link でも動きます。leapmotion でも動いた |
XR_FB_body_tracking | quest, quest2, quest pro | quest link でも動きます。Version 47 が必要な様子(Android側とPC側の両方) |
XR_FB_eye_tracking_social | quest pro | quest link でも動きます。 |
XR_FB_face_tracking | quest pro | quest link でも動きます。 |
Unity の OpenXR
Unity で OpenXR API に直接アクセスするには公式の API からできます。
C の openxr/openxr.h
の情報から Feature を自作して OpenXR Extension にアクセスする方法について。
下記のリポジトリで
XR_EXT_hand_tracking
と XR_FB_hand_tracking_mesh
を実装を発見しました。
quest
, quest pro
の apk
と oculus link
双方で動作します。
本記事は、この実装を読み解いた解説的なものです。
OpenXRFeature
継承と属性で Feature を定義する
#if UNITY_EDITOR
[UnityEditor.XR.OpenXR.Features.OpenXRFeature(UiName = "Hand tracking Extension",
BuildTargetGroups = new[] {
UnityEditor.BuildTargetGroup.Standalone, UnityEditor.BuildTargetGroup.WSA, UnityEditor.BuildTargetGroup.Android },
Company = "Joe M",
Desc = "Enable hand tracking in unity",
DocumentationLink = "https://docs.unity3d.com/Packages/com.unity.xr.openxr@0.1/manual/index.html",
OpenxrExtensionStrings = xr_extension,
Version = "0.0.1",
FeatureId = featureId)]
#endif
public class HandTrackingFeature : OpenXRFeature
{
public const string featureId = "com.joemarshall.handtracking"; // Unique な ID
public const string xr_extension = "XR_EXT_hand_tracking"; // OPENXR の拡張名。拡張を使わないときは無くて良い。
}
OpenXRFeature を有効化する
edit - ProjectSettings - XR Plug-in Management - OpenXR
OpenXRFeature にアクセスする
// MonoBehaviour.Start で取得するとよい
var feature = OpenXRSettings.Instance.GetFeature<HandTrackingFeature>();
feature はシングルトン的にひとつ存在する。
のだけど、手違いで複数のローダーが Asset に存在したりすると多重に生成されて変な動きをするかもしれない(した。)
OpenXRFeature のライフサイクル
OnInstanceCreate
拡張が有効か問い合わせて無かったら false を返すことで、Feature を無効にできる。
override protected bool OnInstanceCreate(ulong xrInstance)
{
instance_ = xrInstance;
if (!OpenXRRuntime.IsExtensionEnabled(xr_extension))
{
Debug.LogWarning($"{xr_extension} is not enabled.");
// Return false here to indicate the system should disable your feature for this execution.
// Note that if a feature is marked required, returning false will cause the OpenXRLoader to abort and try another loader.
return false;
}
return true;
}
機種判定じゃなくて、拡張判定にした方が対応する機種を増やせそう
https://github.khronos.org/OpenXR-Inventory/extension_support.html#matrix
拡張が無効な場合。
- プラットフォームあるいはデバイスに拡張が無い
- 拡張がアプリに許可されていない(quest pro の eye tracking など)
同じデバイスでも Runtime によって拡張が変わる
- Oculus 経由と Steam 経由で拡張の内容がわりと違う
hand tracking
(quest1,2,pro), eye tracking
(quest pro), face tracking
(quest pro) などは oculus link 経由でも有効化できる。
OnSessionBegin
関数ポインタを取得
OpenXR の C
の API そのまま。xrGetInstanceProcAddr 関数で関数ポインタを得る
var getInstanceProcAddr = Marshal.GetDelegateForFunctionPointer<PFN_xrGetInstanceProcAddr>(xrGetInstanceProcAddr);
Func<string, IntPtr> getAddr = (string name) =>
{
IntPtr ptr;
getInstanceProcAddr(instance_, name, out ptr);
return ptr;
};
xrCreateHandTrackerEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrCreateHandTrackerEXT>(getAddr("xrCreateHandTrackerEXT"));
xrDestroyHandTrackerEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrDestroyHandTrackerEXT>(getAddr("xrDestroyHandTrackerEXT"));
xrLocateHandJointsEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrLocateHandJointsEXT>(getAddr("xrLocateHandJointsEXT"));
handle初期化など
XR_EXT_hand_tracking
では右手と左手の handle
ここで初期化する。
MainLoop
XR_EXT_hand_tracking
では右手と左手の handle
を使って joint
の情報を得る。
SessionEnd
XR_EXT_hand_tracking
では右手と左手の handle
を開放する。
SessionBegin と SessionEnd は何度も起きる場合がある。
例えば HMD を外して、再び装着した場合などに再度始まる様子。
DestoryInstance
個別の技
xrWaitFrame の呼び出しに割り込んで FrameTime を得る
GetInstanceProcAddr
の呼び出しに割り込むことができる。
xrWaitFrame
が来たときに別の関数でラップして、その関数ポインターを返すことで XrFrameState
を横取りできる。
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
long XrFrameState.predictedDisplayTime
が HandTracking で joint を得るのに必要なのだが、得る方法が他に無い?
Delegate詳細
Marshal.GetDelegateForFunctionPointer
で IntPtr(Cの関数ポインター)
から Delegate を作る。
xrCreateHandTrackerEXT_ = Marshal.GetDelegateForFunctionPointer<Type_xrCreateHandTrackerEXT>(getAddr("xrCreateHandTrackerEXT"));
C 関数の呼び出しの作法で定義を作る。
/*typedef struct XrHandTrackerCreateInfoEXT {
XrStructureType type;
const void* next;
XrHandEXT hand;
XrHandJointSetEXT handJointSet;
} XrHandTrackerCreateInfoEXT;*/
[StructLayout(LayoutKind.Sequential)]
internal struct XrHandTrackerCreateInfoEXT
{
public int stype;
public IntPtr next;
public int hand;
public int handJointSet;
}
/*XrResult xrCreateHandTrackerEXT(
XrSession session,
const XrHandTrackerCreateInfoEXT* createInfo,
XrHandTrackerEXT* handTracker);*/
internal delegate int Type_xrCreateHandTrackerEXT(ulong session, in XrHandTrackerCreateInfoEXT createInfo, out ulong tracker);
下記の仕様もしくは、ヘッダを参照する。
まだ、Khronos
の仕様に来ていない新しい拡張はベンダーの SDK のドキュメントやヘッダーに定義がある。
XR_FB_eye_tracking_social
など。