Table of Contents

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

Ao criar uma extensão de entrada de dados de imagem e movimento do dispositivo, os desenvolvedores podem estender o EasyAR Sense com implementações personalizadas de câmera, permitindo suporte a dispositivos específicos de head-mounted ou outros dispositivos de entrada. O conteúdo a seguir apresenta as etapas e considerações para criar uma extensão de entrada de dados de imagem e movimento do dispositivo.

Antes de começar

Criar fonte de dados de quadro externa

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

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

public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}

Ao criar uma extensão para headset, você pode usar o template com.easyar.sense.ext.hmdtemplate e modificá-lo com base nesse template. Este template está contido no pacote compactado do plugin Unity para EasyAR, obtido no site da EasyAR.

Definição de dispositivo

Reescreva IsHMD para definir se o dispositivo é um dispositivo de exibição montado na cabeça.

Por exemplo, defina como true em um dispositivo de exibição montado na cabeça.

public override bool IsHMD { get => true; }

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

Por exemplo, a exibição padrão em um dispositivo de exibição montado na cabeça Display.DefaultHMDDisplay define a rotação da exibição como 0.

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

Disponibilidade

Substitua 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 sessão, substitua a corrotina CheckAvailability() para bloquear o processo de montagem até que a disponibilidade seja determinada.

Session origem

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

Se OriginType for Custom, também será necessário reescrever 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, é necessário substituir 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 substituir DeviceCameras para fornecer informações da câmera física do dispositivo. Esses dados são usados ao inserir dados de quadros da câmera. Deve ser concluído 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;
}

Substitua CameraFrameStarted para fornecer o identificador de início do quadro da câmera.

Por exemplo:

protected override bool CameraFrameStarted => started;

Início e parada da sessão

Substitua OnSessionStart(ARSession) e então faça a inicialização específica para AR. É necessário garantir que base.OnSessionStart seja chamado primeiro.

Por exemplo:

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

Este é o local apropriado para abrir a câmera do dispositivo (como câmera RGB ou VST, etc.), especialmente se essas câmeras não foram projetadas para ficarem sempre abertas. Também é o local adequado para obter dados de calibração que não mudarão durante todo o ciclo de vida. Às vezes, pode ser necessário esperar que o dispositivo esteja pronto ou que os dados sejam atualizados antes que eles possam ser obtidos.

Além disso, este é um local adequado para iniciar um loop de entrada de dados. Você também pode escrever esse loop em Update() ou em outro método, especialmente quando os dados precisam ser obtidos em um momento específico da ordem de execução do Unity. Não insira dados até que a sessão esteja pronta (ready).

Se necessário, você pode ignorar o processo de inicialização e verificar os dados a cada atualização, isso depende totalmente das necessidades específicas.

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

Substitua OnSessionStop() e libere os recursos. É necessário garantir que base.OnSessionStop seja chamado.

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

Entrada de dados de quadro da câmera

Após 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]),
    };
    // NOTA: Use o status de rastreamento real quando a exposição da câmera for possível ao escrever sua própria fonte de quadro de 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);
    }
}
Cuidado

Não se esqueça de executar Dispose() ou liberar Image, Buffer e outros dados relacionados por meio de mecanismos como using após o uso. Caso contrário, ocorrerá um grave vazamento de memória, e a obtenção do buffer do buffer pool também pode falhar.

Inserir dados de quadro de renderização

Após os dados do dispositivo estarem prontos, chame HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) a cada quadro de renderização para inserir os dados do 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