Table of Contents

Criar uma extensão de entrada de imagem e dados de movimento do dispositivo

Ao criar uma extensão de entrada de imagem e dados de movimento do dispositivo, os desenvolvedores podem estender uma implementação de câmera personalizada para o EasyAR Sense e oferecer suporte a headsets específicos ou outros dispositivos de entrada. O conteúdo a seguir apresenta as etapas e os pontos de atenção para criar uma extensão de entrada de imagem e dados de movimento do dispositivo.

Antes de começar

Criar classe de fonte de dados de quadros externa

Ambas são subclasses de MonoBehaviour, e o nome do arquivo deve ser igual ao nome da classe.

Por exemplo, criando uma extensão de entrada de dispositivo 6DoF:

public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}

Ao criar uma extensão para headset, você pode usar o modelo com.easyar.sense.ext.hmdtemplate e modificá-lo. Esse modelo está dentro do pacote compactado do plugin Unity baixado do site da EasyAR.

Definição do dispositivo

Sobrescreva IsHMD para definir se o dispositivo é um headset.

Por exemplo, defina como true em um headset.

public override bool IsHMD { get => true; }

Sobrescreva Display para definir a exibição do dispositivo.

Por exemplo, em um headset, use as informações de exibição padrão Display.DefaultHMDDisplay, o que define a rotação da exibição como 0.

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

Disponibilidade

Sobrescreva IsAvailable para definir se o dispositivo está disponível.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Se IsAvailable não puder ser determinado durante a montagem da session, sobrescreva a corrotina CheckAvailability() para bloquear o processo de montagem até que seja possível determinar a disponibilidade.

Origem da session

Sobrescreva OriginType para definir o tipo de origem definido pelo SDK do dispositivo.

Se OriginType for Custom, também será necessário sobrescrever Origin.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Câmera virtual

Se OriginType for Custom ou None, será necessário sobrescrever Camera para fornecer uma câmera virtual.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Câmera física

Use o tipo DeviceFrameSourceCamera para sobrescrever DeviceCameras e fornecer informações da câmera física do dispositivo. Esses dados serão usados ao inserir dados de quadro da câmera. A criação deve estar concluída quando CameraFrameStarted for true.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Sobrescreva CameraFrameStarted para fornecer o identificador de início da entrada de quadros da câmera.

Por exemplo:

protected override bool CameraFrameStarted => started;

Início e parada da session

Sobrescreva OnSessionStart(ARSession) e faça a inicialização específica de AR. Certifique-se de chamar base.OnSessionStart primeiro.

Por exemplo:

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

Este é um local adequado para abrir a câmera do dispositivo, como uma câmera RGB ou VST, especialmente se essas câmeras não tiverem sido projetadas para permanecer sempre abertas. Também é um local adequado para obter dados de calibração que não mudam durante todo o ciclo de vida. Às vezes, pode ser necessário aguardar o dispositivo ficar pronto ou aguardar a atualização dos dados antes que essas informações possam ser obtidas.

Ao mesmo tempo, este também é um local adequado para iniciar o loop de entrada de dados. Esse loop também pode ser escrito em Update() ou em outros métodos, especialmente quando os dados precisam ser obtidos em um ponto específico da ordem de execução do Unity. Não insira dados antes que a session esteja pronta (ready).

Se necessário, você também pode ignorar o processo de inicialização e verificar os dados a cada atualização; isso depende totalmente dos requisitos específicos.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Sobrescreva OnSessionStop() e libere os recursos. Certifique-se de chamar base.OnSessionStop.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Inserir dados de quadro da câmera

Depois de obter a atualização dos dados de quadro da câmera, chame HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) para inserir os dados de quadro da câmera.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Não se esqueça de executar Dispose() depois do uso ou liberar Image, Buffer e outros dados relacionados por mecanismos como using. Caso contrário, ocorrerão vazamentos graves de memória, e a obtenção de buffers no buffer pool também poderá falhar.

Inserir dados de quadro de renderização

Depois que os dados do dispositivo estiverem prontos, chame HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) a cada quadro de renderização para inserir os dados de quadro de renderização.

Por exemplo, a implementação em RokidFrameSource é a seguinte:

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

Próximos passos

Tópicos relacionados