Creazione di un'estensione di input per immagini e dati di movimento del dispositivo
Creando un'estensione di input per immagini e dati di movimento del dispositivo, gli sviluppatori possono estendere EasyAR Sense con implementazioni personalizzate della fotocamera, supportando così dispositivi di visualizzazione specifici (headset) o altri dispositivi di input. Di seguito sono descritti i passaggi e le considerazioni per creare tale estensione.
Prima di iniziare
- Comprendere i concetti di base di fotocamera, frame di input, ecc.
- Leggere Origine dati frame esterna per informazioni dettagliate sulle interfacce necessarie per creare un'origine dati frame esterna.
- Leggere Dati frame di input esterni per comprendere i dati frame della fotocamera e i dati frame di rendering.
Creazione di una classe sorgente di frame dati esterni
- Se è necessario creare un'estensione di input del dispositivo 6DoF, ereditare ExternalDeviceMotionFrameSource
- Se è necessario creare un'estensione di input del dispositivo 3DoF, ereditare ExternalDeviceRotationFrameSource
Entrambi sono sottoclassi di MonoBehaviour e il nome del file deve corrispondere al nome della classe.
Ad esempio, creare un'estensione di input del dispositivo 6DoF:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Durante la creazione di un'estensione per visore, è possibile utilizzare il template com.easyar.sense.ext.hmdtemplate e modificarlo sulla base di questo. Questo template si trova all'interno del pacchetto zip del plugin Unity scaricato dal sito web di EasyAR.
Definizione del dispositivo
Sovrascrivi IsHMD per definire se il dispositivo è un visore (head-mounted display).
Ad esempio, su un visore, imposta true.
public override bool IsHMD { get => true; }
Sovrascrivi Display per definire lo schermo del dispositivo.
Ad esempio, su un visore, le informazioni di visualizzazione predefinite Display.DefaultHMDDisplay, che definisce la rotazione dello schermo come 0.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Disponibilità
Sovrascrivi IsAvailable per definire se il dispositivo è disponibile.
Ad 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 sovrascrivere la coroutine CheckAvailability() per bloccare il processo di assemblaggio finché non viene determinata la disponibilità.
Sessione origine
Sovrascrivi OriginType per definire il tipo di origine definito dall'SDK del dispositivo.
Se OriginType è Custom, è necessario sovrascrivere anche Origin.
Ad 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 o None, è necessario sovrascrivere Camera per fornire una fotocamera virtuale.
Ad esempio, l'implementazione in RokidFrameSource è la seguente:
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Telecamera fisica
Utilizza il tipo DeviceFrameSourceCamera per sovrascrivere DeviceCameras per fornire informazioni sulla telecamera fisica del dispositivo. Questi dati vengono utilizzati durante l'input dei frame della telecamera. La creazione deve essere completata quando CameraFrameStarted è true.
Ad 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;
}
Sovrascrivi CameraFrameStarted per fornire l'identificatore di inizio input del frame della telecamera.
Ad esempio:
protected override bool CameraFrameStarted => started;
Avvio e arresto della sessione
Sovrascrivere OnSessionStart(ARSession) quindi effettuare l'inizializzazione specifica per la realtà aumentata. È necessario assicurarsi di chiamare prima base.OnSessionStart.
Ad esempio:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
Qui è il posto giusto per aprire le telecamere del dispositivo (ad esempio telecamera RGB o VST), specialmente se non sono progettate per rimanere sempre accese. È anche il luogo adatto per ottenere dati di calibrazione che non cambiano durante tutto il ciclo di vita. A volte potrebbe essere necessario attendere che il dispositivo sia pronto o che i dati si aggiornino prima che siano disponibili.
Questo è anche un punto adatto per avviare cicli di input dati. Puoi anche scrivere questi cicli in Update() o altri metodi, specialmente quando i dati devono essere acquisiti in un momento specifico dell'ordine di esecuzione di Unity. Non inserire dati finché la sessione non è pronta (ready).
Se necessario, puoi ignorare il processo di avvio e verificare i dati ad ogni aggiornamento, tutto dipende dalle esigenze specifiche.
Ad 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;
}
Sovrascrivere OnSessionStop() e rilasciare le risorse, assicurandosi di chiamare base.OnSessionStop.
Ad 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;
}
Inserimento dei dati del frame della fotocamera
Dopo aver ottenuto un aggiornamento dei dati del frame della fotocamera, chiama HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) per inserire i dati del frame della fotocamera.
Ad 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() o utilizzare meccanismi come using per rilasciare Image, Buffer e altri dati correlati dopo l'uso. Altrimenti si verificherà una grave perdita di memoria e il buffer pool potrebbe fallire nell'acquisizione del buffer.
Inserire i dati del frame di rendering
Dopo che i dati del dispositivo sono pronti, ogni frame di rendering chiama HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) per inserire i dati del frame di rendering.
Ad 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());
}