Creating画像とデバイスモーション入力拡張
画像とデバイスモーション入力拡張を作成することで、開発者はEasyAR Sense向けにカスタムカメラ実装を拡張し、特定のヘッドマウントディスプレイやその他の入力デバイスをサポートできるようになります。以下は、画像とデバイスモーション入力拡張を作成する手順と注意事項です。
開始之前
- カメラ、入力フレーム などの基本概念を理解する。
- 外部フレームデータソース を読み、外部フレームデータソースを作成するために必要な詳細なインターフェース仕様を確認する。
- 外部入力フレームデータ を読み、カメラフレームデータとレンダリングフレームデータを理解する。
外部フレームデータソースクラスの作成
- 6DoF デバイス入力拡張を作成する場合、ExternalDeviceMotionFrameSource を継承する
- 3DoF デバイス入力拡張を作成する場合、ExternalDeviceRotationFrameSource を継承する
これらは両方とも 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 で定義された原点タイプを定義します。
OriginType が Custom の場合、Origin も上書きする必要があります。
例として、RokidFrameSource での実装は以下の通りです:
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
仮想カメラ
もし OriginType が Custom または 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 などのメカニズムを通じて Image や Buffer および関連データを解放するのを忘れないでください。さもないと深刻なメモリリークが発生し、バッファプールからのバッファ取得が失敗する可能性があります。
レンダリングフレームデータの入力
デバイスデータが準備された後、各レンダリングフレームで 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());
}
次のステップ
- ヘッドセット拡張パックを作成する