Creare estensioni di input per immagini e movimento del dispositivo
Creando estensioni di input per immagini e movimento del dispositivo, gli sviluppatori possono estendere EasyAR Sense con implementazioni di fotocamera personalizzate per supportare specifici visori o altri dispositivi di input. Di seguito sono descritti i passaggi e le attenzioni necessarie per creare queste estensioni.
Prima di iniziare
- Conoscere concetti di base come fotocamere e frame di input.
- Leggere Sorgente frame esterna per i dettagli delle interfacce richieste per creare una sorgente frame esterna.
- Leggere Frame di input esterni per conoscere i dati dei frame della fotocamera e dei frame renderizzati.
Creare classi di sorgente frame esterna
- Se è necessario creare un'estensione di input per dispositivi 6DoF, ereditare da ExternalDeviceMotionFrameSource
- Se è necessario creare un'estensione di input per dispositivi 3DoF, ereditare da ExternalDeviceRotationFrameSource
Entrambe sono sottoclassi di MonoBehaviour, e il nome del file deve corrispondere al nome della classe.
Per esempio, per creare un'estensione di input per dispositivi 6DoF:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Quando si crea un'estensione per HMD, si può usare il template com.easyar.sense.ext.hmdtemplate e modificarlo a partire da quello. Il template è incluso nel pacchetto Unity scaricato dal sito EasyAR.
Definizione del dispositivo
Ridefinire IsHMD per definire se il dispositivo è un visore.
Per esempio, impostarlo a true su un visore.
public override bool IsHMD { get => true; }
Ridefinire Display per definire il display del dispositivo.
Per esempio, usare le informazioni del display HMD predefinito Display.DefaultHMDDisplay, che definiscono la rotazione del display a 0.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Disponibilità
Ridefinire IsAvailable per definire se il dispositivo è disponibile.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
Se IsAvailable non può essere determinato durante l'assemblaggio della sessione, è possibile ridefinire la coroutine CheckAvailability() per bloccare il processo di assemblaggio fino a quando la disponibilità non è confermata.
Origine della sessione
Ridefinire OriginType per definire il tipo di origine definito dall'SDK del dispositivo.
Se OriginType è Custom, è necessario anche ridefinire Origin.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
Fotocamera virtuale
Se OriginType è Custom oppure None, è necessario ridefinire Camera per fornire una fotocamera virtuale.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Fotocamera fisica
Usare il tipo DeviceFrameSourceCamera per ridefinire DeviceCameras e fornire le informazioni della fotocamera fisica del dispositivo. Questi dati vengono usati quando si inviano i frame della fotocamera. La creazione deve essere completata quando CameraFrameStarted è true.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
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;
}
Ridefinire CameraFrameStarted per fornire l'indicatore che segnala l'inizio dell'invio dei frame della fotocamera.
Per esempio:
protected override bool CameraFrameStarted => started;
Avvio e arresto della sessione
Ridefinire OnSessionStart(ARSession) ed eseguire l'inizializzazione specifica per l'AR. Assicurarsi di chiamare prima base.OnSessionStart.
Per esempio:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
Questo è il punto adatto per aprire la fotocamera del dispositivo, ad esempio la RGB o la VST, soprattutto se non è pensata per restare sempre aperta. È anche il punto adatto per ottenere dati di calibrazione che non cambiano per tutto il ciclo di vita. A volte, prima di poterli ottenere, può essere necessario attendere che il dispositivo sia pronto o che i dati si aggiornino.
È anche un punto adatto per avviare il ciclo di input dei dati. Questo ciclo può essere scritto anche in Update() o in altri metodi, soprattutto quando i dati devono essere letti in un momento specifico dell'ordine di esecuzione di Unity. Non inviare dati prima che la sessione sia pronta.
Se necessario, si può anche ignorare il processo di avvio ed eseguire controlli dei dati a ogni aggiornamento; dipende interamente dalle esigenze specifiche.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
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;
}
Ridefinire OnSessionStop() e rilasciare le risorse, assicurandosi di chiamare base.OnSessionStop.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
protected override void OnSessionStop()
{
base.OnSessionStop();
RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
started = false;
StopAllCoroutines();
cameraParameters?.Dispose();
cameraParameters = null;
deviceCamera?.Dispose();
deviceCamera = null;
}
Dati del frame di input della fotocamera
Dopo aver ricevuto l'aggiornamento dei dati del frame della fotocamera, chiamare HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) per inviare i dati del frame della fotocamera.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
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);
}
}
Attenzione
Non dimenticare di eseguire Dispose() oppure di rilasciare Image, Buffer e gli altri dati correlati tramite meccanismi come using dopo l'uso. In caso contrario si verificheranno gravi perdite di memoria e il buffer pool potrebbe non riuscire più ad acquisire buffer.
Dati del frame di rendering
Dopo che i dati del dispositivo sono pronti, chiamare HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) per ogni frame di rendering per inviare i dati del frame di rendering.
Per esempio, l'implementazione in RokidFrameSource è la seguente:
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());
}