Table of Contents

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

Erstellen einer externen Frame-Datenquellenklasse

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

Verwandte themen