Membuat ekstensi input data gambar dan gerak perangkat
Dengan membuat ekstensi input data gambar dan gerak perangkat, pengembang dapat memperluas implementasi kamera kustom untuk EasyAR Sense, sehingga mendukung perangkat head-mounted tertentu atau perangkat input lainnya. Konten berikut memperkenalkan langkah-langkah dan pertimbangan untuk membuat ekstensi input data gambar dan gerak perangkat.
Sebelum memulai
- Pahami konsep dasar seperti kamera, frame input.
- Baca Sumber data frame eksternal untuk penjelasan detail antarmuka yang diperlukan dalam membuat sumber data frame eksternal.
- Baca Data frame input eksternal untuk memahami data frame kamera dan data frame rendering.
Membuat kelas sumber data frame eksternal
- Jika perlu membuat ekstensi input perangkat 6DoF, warisi ExternalDeviceMotionFrameSource
- Jika perlu membuat ekstensi input perangkat 3DoF, warisi ExternalDeviceRotationFrameSource
Keduanya adalah subkelas dari MonoBehaviour, nama file harus sama dengan nama kelas.
Contoh, membuat ekstensi input perangkat 6DoF:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Saat membuat ekstensi head-mounted display, Anda dapat menggunakan template com.easyar.sense.ext.hmdtemplate, dan memodifikasinya berdasarkan template tersebut. Template ini terdapat dalam paket terkompresi plugin Unity yang diunduh dari situs web EasyAR.
Definisi perangkat
Timpa IsHMD untuk mendefinisikan apakah perangkat adalah head-mounted display (HMD).
Contohnya, atur sebagai true pada HMD.
public override bool IsHMD { get => true; }
Timpa Display untuk mendefinisikan tampilan perangkat.
Contohnya, pada HMD, informasi tampilan default Display.DefaultHMDDisplay digunakan, yang mendefinisikan rotasi tampilan sebagai 0.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Ketersediaan
Timpa IsAvailable untuk mendefinisikan apakah perangkat tersedia.
Contohnya, implementasi di RokidFrameSource sebagai berikut:
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
Jika IsAvailable tidak dapat ditentukan saat perakitan session, timpa coroutine CheckAvailability() untuk memblokir proses perakitan hingga ketersediaan dapat dipastikan.
Sesi asal
Timpa OriginType untuk mendefinisikan tipe asal yang ditentukan oleh SDK perangkat.
Jika OriginType adalah Custom, timpa juga Origin.
Contoh, implementasi dalam RokidFrameSource sebagai berikut:
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
Kamera virtual
Apabila OriginType adalah Custom atau None, perlu mengganti Camera untuk menyediakan kamera virtual.
Sebagai contoh, implementasi dalam RokidFrameSource adalah sebagai berikut:
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Kamera fisik
Gunakan tipe DeviceFrameSourceCamera untuk mengganti DeviceCameras guna menyediakan informasi kamera fisik perangkat. Data ini digunakan saat memasukkan data frame kamera. Harus selesai dibuat saat CameraFrameStarted bernilai true.
Contohnya, implementasi dalam RokidFrameSource adalah sebagai berikut:
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;
}
Ganti CameraFrameStarted untuk menyediakan penanda mulai input frame kamera.
Contoh:
protected override bool CameraFrameStarted => started;
Memulai dan menghentikan sesi
Override OnSessionStart(ARSession) kemudian lakukan inisialisasi khusus AR. Pastikan untuk memanggil base.OnSessionStart terlebih dahulu.
Contoh:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
Ini adalah tempat yang tepat untuk membuka kamera perangkat (seperti kamera RGB atau kamera VST), terutama jika kamera tersebut tidak dirancang untuk tetap terbuka sepanjang waktu. Ini juga tempat yang tepat untuk mendapatkan data kalibrasi yang tidak akan berubah selama siklus hidup. Terkadang perlu menunggu perangkat siap atau menunggu pembaruan data sebelum data ini dapat diperoleh.
Ini juga tempat yang cocok untuk memulai loop input data. Anda juga dapat menulis loop ini di Update() atau metode lain, terutama jika data perlu diperoleh pada titik waktu tertentu dalam urutan eksekusi Unity. Jangan masukkan data sebelum sesi siap (ready).
Jika perlu, Anda juga dapat mengabaikan proses startup dan memeriksa data pada setiap pembaruan, semuanya tergantung pada kebutuhan spesifik.
Contoh, implementasi di RokidFrameSource adalah sebagai berikut:
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;
}
Override OnSessionStop() dan lepaskan sumber daya. Pastikan untuk memanggil base.OnSessionStop.
Contoh, implementasi di RokidFrameSource adalah sebagai berikut:
protected override void OnSessionStop()
{
base.OnSessionStop();
RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
started = false;
StopAllCoroutines();
cameraParameters?.Dispose();
cameraParameters = null;
deviceCamera?.Dispose();
deviceCamera = null;
}
Memasukkan data frame kamera
Setelah memperbarui data frame kamera, panggil HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) untuk memasukkan data frame kamera.
Contohnya, implementasi di RokidFrameSource sebagai berikut:
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]),
};
// NOTE: Use real tracking status when camera exposure if possible when writing your own device frame source.
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);
}
}
Hati-Hati
Jangan lupa untuk melakukan Dispose() atau melepaskan Image, Buffer dan data terkait lainnya melalui mekanisme seperti using setelah digunakan. Jika tidak, akan terjadi kebocoran memori serius dan buffer pool mungkin gagal memperoleh buffer.
Memasukkan data frame rendering
Setelah data perangkat siap, setiap frame rendering panggil HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) untuk memasukkan data frame rendering.
Contohnya, implementasi di RokidFrameSource sebagai berikut:
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());
}