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
- Entenda conceitos básicos como câmeras e quadros de entrada.
- Leia Fonte de dados de quadros externa para conhecer a descrição detalhada das interfaces necessárias para criar uma fonte de dados de quadros externa.
- Leia Dados de quadro de entrada externos para conhecer os dados de quadro da câmera e os dados de quadro de renderização.
Criar classe de fonte de dados de quadros externa
- Se precisar criar uma extensão de entrada de dispositivo 6DoF, herde de ExternalDeviceMotionFrameSource
- Se precisar criar uma extensão de entrada de dispositivo 3DoF, herde de ExternalDeviceRotationFrameSource
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());
}