Table of Contents

Creación de una extensión de entrada de datos de imagen y movimiento del dispositivo

Al crear una extensión de entrada de datos de imagen y movimiento del dispositivo, los desarrolladores pueden extender EasyAR Sense con implementaciones de cámara personalizadas, lo que permite admitir dispositivos de visualización montados en la cabeza (head-mounted) específicos u otros dispositivos de entrada. El siguiente contenido describe los pasos y las consideraciones para crear una extensión de entrada de datos de imagen y movimiento del dispositivo.

Antes de comenzar

Crear una clase de fuente de datos de fotogramas externa

Ambas son subclases de MonoBehaviour. El nombre del archivo debe coincidir con el nombre de la clase.

Por ejemplo, crear una extensión de entrada de dispositivo de 6DoF:

public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}

Al crear una extensión para auriculares (head-mounted display), puede usar la plantilla com.easyar.sense.ext.hmdtemplate y modificarla sobre esa base. Esta plantilla se encuentra dentro del paquete comprimido del plugin de Unity que se descarga desde el sitio web de EasyAR.

Definición de dispositivo

Sobreescribir IsHMD para definir si el dispositivo es un casco de realidad virtual.

Por ejemplo, configurar como true en un casco de realidad virtual.

public override bool IsHMD { get => true; }

Sobreescribir Display para definir la pantalla del dispositivo.

Por ejemplo, la información de pantalla predeterminada Display.DefaultHMDDisplay en un casco, que define la rotación de pantalla como 0.

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

Disponibilidad

Reescribe IsAvailable para definir si el dispositivo está disponible.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Si IsAvailable no puede determinarse durante el ensamblado de la sesión, puedes reescribir la corrutina CheckAvailability() para bloquear el proceso de ensamblado hasta que se determine la disponibilidad.

Origen de sesión

Reescribe OriginType para definir el tipo de origen definido por el SDK del dispositivo.

Si OriginType es Custom, también necesitas reescribir Origin.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Cámara virtual

Si OriginType es Custom o None, es necesario anular Camera para proporcionar una cámara virtual.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Cámara física

Usa el tipo DeviceFrameSourceCamera para reemplazar DeviceCameras y proporcionar información de la cámara física del dispositivo. Estos datos se utilizan al ingresar datos de fotogramas de la cámara. Debe completarse cuando CameraFrameStarted es true.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Reemplaza CameraFrameStarted para proporcionar el identificador de inicio de fotograma de cámara.

Por ejemplo:

protected override bool CameraFrameStarted => started;

Inicio y detención de la sesión

Sobrescribe OnSessionStart(ARSession) y luego realiza la inicialización específica de AR. Asegúrate de llamar primero a base.OnSessionStart.

Por ejemplo:

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

Este es el lugar adecuado para activar cámaras de dispositivo (como cámaras RGB o VST), especialmente si no están diseñadas para permanecer siempre encendidas. También es el lugar para obtener datos de calibración que no cambien durante todo el ciclo de vida. A veces puede ser necesario esperar a que el dispositivo esté preparado o a que se actualicen los datos antes de que estén disponibles.

Además, este es un lugar adecuado para iniciar bucles de entrada de datos. También puedes escribir este bucle en Update() u otros métodos, especialmente si los datos deben obtenerse en un momento específico del orden de ejecución de Unity. No introduzcas datos hasta que la sesión esté lista (ready).

Si es necesario, puedes omitir el proceso de inicio y verificar los datos en cada actualización, dependiendo completamente de los requisitos específicos.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Sobrescribe OnSessionStop() y libera los recursos. Asegúrate de llamar a base.OnSessionStop.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Entrada de datos de fotograma de cámara

Después de recibir una actualización de datos de fotograma de cámara, llama a HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) para introducir los datos del fotograma de la cámara.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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]),
    };
    // NOTA: Utiliza el estado de seguimiento real cuando la exposición de la cámara sea posible al escribir tu propia fuente de fotogramas del dispositivo.
    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);
    }
}

[!PRECAUCIÓN] No olvides ejecutar Dispose() después del uso o liberar Image, Buffer y otros datos relacionados mediante mecanismos como using. De lo contrario, ocurrirán graves fugas de memoria y la obtención de buffer del pool de buffers podría fallar.

Introducción de datos de fotograma de renderizado

Después de que los datos del dispositivo estén listos, cada fotograma de renderizado llama a HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) para introducir datos de fotograma de renderizado.

Por ejemplo, la implementación en RokidFrameSource es la siguiente:

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

Pasos siguientes

Temas relacionados