Table of Contents

Membuat ekstensi input data gambar dan gerakan perangkat

Dengan membuat ekstensi input data gambar dan gerakan perangkat, pengembang dapat memperluas implementasi kamera kustom untuk EasyAR Sense agar mendukung headset tertentu atau perangkat input lainnya. Bagian berikut memperkenalkan langkah dan hal yang perlu diperhatikan saat membuat ekstensi input data gambar dan gerakan perangkat.

Sebelum memulai

Membuat kelas sumber data frame eksternal

Keduanya adalah subclass dari MonoBehaviour, dan nama file harus sama dengan nama kelas.

Misalnya, membuat ekstensi input perangkat 6DoF:

public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}

Saat membuat ekstensi headset, Anda dapat menggunakan template com.easyar.sense.ext.hmdtemplate dan memodifikasinya berdasarkan template tersebut. Template ini berada di dalam paket zip plugin Unity yang diunduh dari situs web EasyAR.

Definisi perangkat

Override IsHMD untuk mendefinisikan apakah perangkat adalah headset.

Misalnya, atur ke true pada headset.

public override bool IsHMD { get => true; }

Override Display untuk mendefinisikan tampilan perangkat.

Misalnya, pada headset gunakan informasi tampilan default Display.DefaultHMDDisplay, yang mendefinisikan rotasi tampilan sebagai 0.

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

Ketersediaan

Override IsAvailable untuk mendefinisikan apakah perangkat tersedia.

Misalnya, implementasi dalam RokidFrameSource adalah sebagai berikut:

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

Jika IsAvailable tidak dapat ditentukan saat perakitan session, Anda dapat meng-override coroutine CheckAvailability() untuk memblokir proses perakitan hingga ketersediaannya dipastikan.

Titik asal session

Override OriginType untuk mendefinisikan jenis titik asal yang didefinisikan oleh SDK perangkat.

Jika OriginType adalah Custom, Anda juga perlu meng-override Origin.

Misalnya, implementasi dalam RokidFrameSource adalah sebagai berikut:

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

Kamera virtual

Jika OriginType adalah Custom atau None, Anda perlu meng-override Camera untuk menyediakan kamera virtual.

Misalnya, implementasi dalam RokidFrameSource adalah sebagai berikut:

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

Kamera fisik

Gunakan tipe DeviceFrameSourceCamera untuk meng-override DeviceCameras guna menyediakan informasi kamera fisik perangkat. Data ini akan digunakan saat memasukkan data frame kamera. Pembuatan harus sudah selesai saat CameraFrameStarted bernilai true.

Misalnya, 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;
}

Override CameraFrameStarted untuk menyediakan penanda bahwa input frame kamera telah dimulai.

Misalnya:

protected override bool CameraFrameStarted => started;

Memulai dan menghentikan session

Override OnSessionStart(ARSession) lalu lakukan inisialisasi khusus AR. Pastikan untuk memanggil base.OnSessionStart terlebih dahulu.

Misalnya:

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

Di sinilah tempat yang cocok untuk membuka kamera perangkat, seperti kamera RGB atau kamera VST, terutama jika kamera tersebut tidak dirancang untuk selalu aktif. Ini juga tempat yang cocok untuk memperoleh data kalibrasi yang tidak berubah sepanjang siklus hidup. Terkadang, sebelum data ini dapat diperoleh, perlu menunggu perangkat siap atau menunggu pembaruan data.

Pada saat yang sama, ini juga tempat yang cocok untuk memulai loop input data. Loop ini juga dapat ditulis di Update() atau metode lain, terutama saat data perlu diperoleh pada titik waktu tertentu dalam urutan eksekusi Unity. Jangan memasukkan data sebelum session siap (ready).

Jika diperlukan, proses start juga dapat diabaikan dan pemeriksaan data dilakukan pada setiap update; ini sepenuhnya bergantung pada kebutuhan spesifik.

Misalnya, implementasi dalam 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 resource. Pastikan untuk memanggil base.OnSessionStop.

Misalnya, implementasi dalam 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 memperoleh pembaruan data frame kamera, panggil HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) untuk memasukkan data frame kamera.

Misalnya, implementasi dalam RokidFrameSource adalah 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 menjalankan Dispose() setelah digunakan, atau melepaskan Image, Buffer, dan data terkait lain melalui mekanisme seperti using. Jika tidak, kebocoran memori serius akan terjadi, dan buffer pool juga mungkin gagal memperoleh buffer.

Memasukkan data frame render

Setelah data perangkat siap, panggil HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) pada setiap frame render untuk memasukkan data frame render.

Misalnya, implementasi dalam RokidFrameSource adalah 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 berikutnya

Topik terkait