Erstellung einer Erweiterung für Bild- und Gerätebewegungsdaten-Eingabe
Durch die Erstellung einer Erweiterung für Bild- und Gerätebewegungsdaten-Eingabe können Entwickler kundenspezifische Kamera-Implementierungen für EasyAR Sense bereitstellen, um spezifische kopfmontierte Anzeigegeräte oder andere Eingabegeräte zu unterstützen. Die folgenden Inhalte beschreiben die Schritte und Überlegungen zur Erstellung einer solchen Erweiterung für Bild- und Gerätebewegungsdaten-Eingabe.
Bevor Sie beginnen
- Machen Sie sich mit grundellen Konzepten wie Kamera, Eingaberahmen vertraut.
- Lesen Sie Externe Frame-Datenquellen für detaillierte Schnittstellenbeschreibungen zur Erstellung externer Frame-Datenquellen.
- Lesen Sie Externe Eingabeframedaten für Informationen zu Kameraframedaten und Rendering-Framedaten.
Erstellen einer externen Frame-Datenquellenklasse
- Wenn Sie eine 6DoF-Geräteeingabeerweiterung erstellen müssen, erben Sie von ExternalDeviceMotionFrameSource
- Wenn Sie eine 3DoF-Geräteeingabeerweiterung erstellen müssen, erben Sie von ExternalDeviceRotationFrameSource
Beide sind Unterklassen von MonoBehaviour. Der Dateiname sollte mit dem Klassennamen übereinstimmen.
Beispiel: Erstellen einer 6DoF-Geräteeingabeerweiterung:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Beim Erstellen von Head-Mounted-Display-Erweiterungen können Sie die Vorlage com.easyar.sense.ext.hmdtemplate verwenden und auf deren Basis Änderungen vornehmen. Diese Vorlage befindet sich im Unity-Plugin-Paket, das Sie vom EasyAR-Webseite-Download erhalten.
Gerätedefinition
Überschreiben Sie IsHMD, um zu definieren, ob das Gerät ein Head-Mounted-Display ist.
Zum Beispiel, auf einem Head-Mounted-Display auf true setzen.
public override bool IsHMD { get => true; }
Überschreiben Sie Display, um die Anzeige des Geräts zu definieren.
Zum Beispiel, auf einem Head-Mounted-Display die standardmäßige Display.DefaultHMDDisplay-Anzeigeinformation, die die Anzeigerotation auf 0 definiert.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Verfügbarkeit
Überschreiben Sie IsAvailable, um zu definieren, ob das Gerät verfügbar ist.
Beispielsweise ist die Implementierung in RokidFrameSource wie folgt:
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
Falls IsAvailable während der Sitzungszusammenstellung nicht bestimmt werden kann, können Sie die Coroutine CheckAvailability() überschreiben, um den Zusammenstellungsprozess zu blockieren, bis die Verfügbarkeit feststeht.
Session ursprung
Überschreiben Sie OriginType, um den vom Geräte-SDK definierten Ursprungstyp festzulegen.
Wenn OriginType auf Custom gesetzt ist, müssen Sie auch Origin überschreiben.
Beispielsweise wird dies in RokidFrameSource wie folgt implementiert:
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
Virtuelle kamera
Wenn OriginType Custom oder None ist, muss Camera überschrieben werden, um eine virtuelle Kamera bereitzustellen.
Beispielsweise wird dies in RokidFrameSource wie folgt implementiert:
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Physikalische kameras
Verwenden Sie den Typ DeviceFrameSourceCamera, um DeviceCameras zu überschreiben und Informationen über die physikalischen Kameras des Geräts bereitzustellen. Diese Daten werden bei der Eingabe von Kamerarahmendaten verwendet. Die Erstellung muss abgeschlossen sein, wenn CameraFrameStarted true ist.
Beispielsweise erfolgt die Implementierung in RokidFrameSource wie folgt:
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;
}
Überschreiben Sie CameraFrameStarted, um den Indikator für den Beginn der Kamerarahmeneingabe bereitzustellen.
Beispiel:
protected override bool CameraFrameStarted => started;
Session-start und -stop
Überschreiben Sie OnSessionStart(ARSession) und führen Sie dann AR-spezifische Initialisierungsaufgaben durch. Stellen Sie sicher, dass base.OnSessionStart zuerst aufgerufen wird.
Beispiel:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
Dies ist der geeignete Ort, um Gerätekameras (wie RGB-Kameras oder VST-Kameras) zu aktivieren, insbesondere wenn diese nicht für einen dauerhaften Betrieb ausgelegt sind. Ebenso ist dies der richtige Platz, um Kalibrierdaten abzurufen, die sich während der gesamten Lebensdauer nicht ändern. Manchmal kann es notwendig sein zu warten, bis das Gerät bereit ist oder die Daten aktualisiert wurden, bevor auf sie zugegriffen werden kann.
Gleichzeitig ist dies ein geeigneter Ort, um eine Daten-Eingabeschleife zu starten. Sie können diese Schleife auch in Update() oder anderen Methoden implementieren, insbesondere wenn Daten zu einem bestimmten Zeitpunkt im Unity-Ausführungsfluss benötigt werden. Geben Sie keine Daten ein, bevor die Session bereit (ready) ist.
Falls erforderlich, können Sie den Startvorgang auch ignorieren und bei jedem Update eine Datenprüfung durchführen – dies hängt vollständig von den spezifischen Anforderungen ab.
Beispielsweise ist die Implementierung in RokidFrameSource wie folgt:
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;
}
Überschreiben Sie OnSessionStop() und geben Sie Ressourcen frei. Stellen Sie sicher, dass base.OnSessionStop aufgerufen wird.
Beispielsweise sieht die Implementierung in RokidFrameSource so aus:
protected override void OnSessionStop()
{
base.OnSessionStop();
RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
started = false;
StopAllCoroutines();
cameraParameters?.Dispose();
cameraParameters = null;
deviceCamera?.Dispose();
deviceCamera = null;
}
Eingabe von kamerarahmendaten
Nachdem ein Update der Kamerarahmendaten empfangen wurde, rufen Sie HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) auf, um die Kamerarahmendaten einzugeben.
Beispielsweise erfolgt die Implementierung in RokidFrameSource wie folgt:
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]),
};
// HINWEIS: Verwenden Sie nach Möglichkeit den echten Tracking-Status während der Kamerabelichtung, wenn Sie Ihre eigene Geräterahmendatenquelle schreiben.
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);
}
}
Vorsicht
Vergessen Sie nicht, Dispose() auszuführen oder die Ressourcen über einen using-Mechanismus freizugeben, nachdem Sie Image, Buffer und andere zugehörige Daten verwendet haben. Andernfalls kann es zu schweren Speicherlecks kommen, und das Abrufen von Puffern aus dem Buffer-Pool könnte fehlschlagen.
Render-Frame-Daten eingeben
Nachdem die Gerätedaten bereit sind, rufen Sie pro Render-Frame HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) auf, um Render-Frame-Daten einzugeben.
Beispielsweise erfolgt die Implementierung in RokidFrameSource wie folgt:
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());
}
Nächste schritte
- Erstellen Sie das Kopfhörer-Erweiterungspaket