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
- Compreenda conceitos básicos como câmeras, quadro de entrada.
- Leia Fonte de dados de quadro externo para instruções detalhadas sobre as interfaces necessárias para criar uma fonte de dados de quadro externo.
- Leia Dados de quadro de entrada externos para entender os dados de quadro da câmera e os dados de quadro de renderização.
Criar fonte de dados de quadro 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. 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());
}