Table of Contents

Creating画像とデバイスモーション入力拡張

画像とデバイスモーション入力拡張を作成することで、開発者はEasyAR Sense向けにカスタムカメラ実装を拡張し、特定のヘッドマウントディスプレイやその他の入力デバイスをサポートできるようになります。以下は、画像とデバイスモーション入力拡張を作成する手順と注意事項です。

開始之前

外部フレームデータソースクラスの作成

これらは両方とも MonoBehaviour のサブクラスであり、ファイル名はクラス名と同じにする必要があります。

例: 6DoF デバイス入力拡張の作成:

public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}

ヘッドマウントディスプレイ (HMD) 拡張を作成する際には、com.easyar.sense.ext.hmdtemplate テンプレートを使用し、それを基に変更を加えることができます。このテンプレートは、EasyAR ウェブサイトからダウンロードできる Unity プラグインの圧縮ファイル内に含まれています。

デバイス定義

IsHMD をオーバーライドして、デバイスがヘッドマウントディスプレイ(HMD)かどうかを定義します。

例えば、ヘッドマウントディスプレイ(HMD)では true に設定します。

public override bool IsHMD { get => true; }

Display をオーバーライドして、デバイスのディスプレイを定義します。

例えば、ヘッドマウントディスプレイ(HMD)ではデフォルトのディスプレイ情報 Display.DefaultHMDDisplay を使用します。これによりディスプレイの回転が0に定義されます。

protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;

利用可能性

IsAvailable をオーバーライドして、デバイスが利用可能かどうかを定義します。

例として、RokidFrameSource での実装は以下の通りです:

protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;

セッションのアセンブリ時に IsAvailable が判断できない場合、CheckAvailability() コルーチンをオーバーライドし、利用可能性が確定するまでアセンブリプロセスをブロックできます。

Session 原点

OriginType を上書きして、デバイス SDK で定義された原点タイプを定義します。

OriginTypeCustom の場合、Origin も上書きする必要があります。

例として、RokidFrameSource での実装は以下の通りです:

protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
    hasUXRComponents ? DeviceOriginType.None :
#endif
    DeviceOriginType.XROrigin;

仮想カメラ

もし OriginTypeCustom または None である場合、Camera をオーバーライドして仮想カメラを提供する必要があります。

例えば、RokidFrameSource での実装は次のとおりです:

protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;

物理カメラ

DeviceFrameSourceCamera タイプを使用して DeviceCameras をオーバーライドし、デバイスの物理カメラ情報を提供します。このデータは入力カメラフレームデータで使用されます。CameraFrameStarted が true の場合、作成が完了している必要があります。

例えば、RokidFrameSource での実装は次のとおりです:

private DeviceFrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };

{
    var imageDimensions = new int[2];
    RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);
    size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
    deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
    started = true;
}

CameraFrameStarted をオーバーライドして、カメラフレーム入力開始のフラグを提供します。

例えば:

protected override bool CameraFrameStarted => started;

Session start and stop

OnSessionStart(ARSession) をオーバーライドし、AR固有の初期化作業を実行します。base.OnSessionStart を最初に呼び出すことを確認してください。

例:

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    StartCoroutine(InitializeCamera());
}

ここは、デバイスカメラ(RGBカメラやVSTカメラなど)を開くのに適した場所です。特に、これらのカメラが常にオンにされるよう設計されていない場合に該当します。また、ライフサイクル全体で変化しないキャリブレーションデータを取得するのにも適しています。デバイスの準備が整うのを待ったり、データの更新を待つ必要がある場合があります。

同時に、データ入力ループを開始するのにも適した場所です。データをUnityの実行順序の特定のタイミングで取得する必要がある場合は、Update() やその他のメソッドでこのループを記述することもできます。セッションが準備完了(ready)になるまでデータを入力しないでください。

必要に応じて、起動プロセスを無視し、毎回の更新でデータチェックを行うこともできます。これは完全に要件次第です。

例として、RokidFrameSource での実装は次のとおりです:

private IEnumerator InitializeCamera()
{
    yield return new WaitUntil(() => (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Detecting && (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Tracking_Paused);

    var focalLength = new float[2];
    RokidExtensionAPI.RokidOpenXR_API_GetFocalLength(focalLength);
    var principalPoint = new float[2];
    RokidExtensionAPI.RokidOpenXR_API_GetPrincipalPoint(principalPoint);
    var distortion = new float[5];
    RokidExtensionAPI.RokidOpenXR_API_GetDistortion(distortion);
    var imageDimensions = new int[2];
    RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);

    size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
    var cameraParamList = new List<float> { focalLength[0], focalLength[1], principalPoint[0], principalPoint[1] }.Concat(distortion.ToList().GetRange(1, 4)).ToList();
    cameraParameters = CameraParameters.tryCreateWithCustomIntrinsics(size.ToEasyARVector(), cameraParamList, CameraModelType.OpenCV_Fisheye, CameraDeviceType.Back, 0).Value;
    deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);

    RokidExtensionAPI.RokidOpenXR_API_OpenCameraPreview(OnCameraDataUpdate);
    started = true;
}

OnSessionStop() をオーバーライドし、リソースを解放します。base.OnSessionStop を呼び出すことを確認してください。

例として、RokidFrameSource での実装は次のとおりです:

protected override void OnSessionStop()
{
    base.OnSessionStop();
    RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
    started = false;
    StopAllCoroutines();
    cameraParameters?.Dispose();
    cameraParameters = null;
    deviceCamera?.Dispose();
    deviceCamera = null;
}

カメラフレームデータの入力

カメラフレームデータの更新を取得後、HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) を呼び出してカメラフレームデータを入力します。

例として、RokidFrameSource での実装は以下の通りです:

private static void OnCameraDataUpdate(IntPtr ptr, int dataSize, ushort width, ushort height, long timestamp)
{
    if (!instance) { return; }
    if (ptr == IntPtr.Zero || dataSize == 0 || timestamp == 0) { return; }
    if (timestamp == instance.curTimestamp) { return; }

    instance.curTimestamp = timestamp;

    RokidExtensionAPI.RokidOpenXR_API_GetHistoryCameraPhysicsPose(timestamp, positionCache, rotationCache);
    var pose = new Pose
    {
        position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
        rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
    };
    // 注:独自のデバイスフレームソースを作成する際は、可能であればカメラ露光時の実際のトラッキングステータスを使用してください。
    var trackingStatus = ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus();

    var size = instance.size;
    var pixelSize = instance.size;
    var pixelFormat = PixelFormat.Gray;
    var yLen = pixelSize.x * pixelSize.y;
    var bufferBlockSize = yLen;

    var bufferO = instance.TryAcquireBuffer(bufferBlockSize);
    if (bufferO.OnNone) { return; }

    var buffer = bufferO.Value;
    buffer.tryCopyFrom(ptr, 0, 0, bufferBlockSize);

    using (buffer)
    using (var image = Image.create(buffer, pixelFormat, size.x, size.y, pixelSize.x, pixelSize.y))
    {
        instance.HandleCameraFrameData(instance.deviceCamera, timestamp * 1e-9, image, instance.cameraParameters, pose, trackingStatus);
    }
}
注意

使用後に Dispose() を実行するか、using などのメカニズムを通じて ImageBuffer および関連データを解放するのを忘れないでください。さもないと深刻なメモリリークが発生し、バッファプールからのバッファ取得が失敗する可能性があります。

レンダリングフレームデータの入力

デバイスデータが準備された後、各レンダリングフレームで HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) を呼び出して、レンダリングフレームデータを入力します。

例えば、 RokidFrameSource での実装方法は次のとおりです:

protected void LateUpdate()
{
    if (!started) { return; }
    if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Detecting) { return; }
    if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Tracking_Paused) { return; }

    InputRenderFrameMotionData();
}

private void InputRenderFrameMotionData()
{
    var timestamp = RokidExtensionAPI.RokidOpenXR_API_GetCameraPhysicsPose(positionCache, rotationCache);
    var pose = new Pose
    {
        position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
        rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
    };
    if (timestamp == 0) { return; }
    HandleRenderFrameData(timestamp * 1e-9, pose, ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus());
}

次のステップ

関連トピック