Table of Contents

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

Membuat kelas sumber data frame eksternal

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());
}

Langkah selanjutnya

Topik terkait