Start · Sprachen · JavaScript · Referenz · XRFrame

XRFrame

Schnittstelle

Ein <code>XRFrame</code> ist eine Momentaufnahme des Tracking-Zustands einer <code>XRSession</code> und wird an den Callback von <code>XRSession.requestAnimationFrame()</code> übergeben.

seit JavaScript WebXR Device API – Chrome/Edge 79+, Samsung Intern Kategorie: webapi

Signatur

interface XRFrame

Beschreibung

XRFrame steht für einen einzelnen Animationsframe einer WebXR-Sitzung. Der Browser erzeugt das Objekt und übergibt es zusammen mit einem Zeitstempel an den Callback von XRSession.requestAnimationFrame(). Alle Posen, die man aus dem Frame abfragt, beziehen sich auf denselben Zeitpunkt. Kopf, Controller und Anker bleiben dadurch innerhalb eines Frames zueinander stimmig.

Die wichtigsten Mitglieder:

  • frame.session: die XRSession, zu der der Frame gehört (nur lesbar).
  • frame.predictedDisplayTime: der vorhergesagte Zeitpunkt in Millisekunden, zu dem der Frame angezeigt wird (nur lesbar).
  • frame.getViewerPose(referenceSpace): liefert eine XRViewerPose mit den Blickpunkten (views) des Nutzers, zum Beispiel ein Blickpunkt pro Auge. Kann null sein, wenn das Tracking verloren ist.
  • frame.getPose(space, baseSpace): liefert die XRPose eines beliebigen XRSpace (etwa inputSource.gripSpace oder targetRaySpace) relativ zu einem Basisraum.
  • Erweiterungen, die nur mit dem jeweiligen Modul verfügbar sind: createAnchor(), trackedAnchors (Anchors), getHitTestResults() (Hit Test), getJointPose()/fillJointRadii()/fillPoses() (Hand Input), getDepthInformation() (Depth Sensing), getLightEstimate() (Lighting Estimation), detectedPlanes (Plane Detection).

Einen XRFrame baut man nie selbst, und es gibt keinen Konstruktor. Man arbeitet mit ihm nur innerhalb des Frame-Callbacks. Der typische Ablauf: im Callback sofort den nächsten Frame anfordern, dann die Betrachter-Pose holen, für jede XRView Viewport und Matrizen setzen und die Szene zeichnen. Eingabegeräte fragt man über getPose() ab.

Rückgabewert

Typ
XRViewerPose | XRPose | null
Beschreibung
getViewerPose() gibt eine XRViewerPose zurück, getPose() eine XRPose. Beide liefern null, wenn sich die Pose gerade nicht bestimmen lässt.

Beispiele

Render-Schleife mit getViewerPose()

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2', { xrCompatible: true });

const startXR = async () => {
  const session = await navigator.xr.requestSession('immersive-vr');
  await session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
  const refSpace = await session.requestReferenceSpace('local-floor');

  const onFrame = (time, frame) => {
    // Nächsten Frame sofort anfordern
    frame.session.requestAnimationFrame(onFrame);

    const pose = frame.getViewerPose(refSpace);
    if (!pose) return; // Tracking verloren

    const layer = frame.session.renderState.baseLayer;
    gl.bindFramebuffer(gl.FRAMEBUFFER, layer.framebuffer);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    for (const view of pose.views) {
      const vp = layer.getViewport(view);
      gl.viewport(vp.x, vp.y, vp.width, vp.height);
      // drawScene(view.projectionMatrix, view.transform.inverse.matrix);
    }
  };

  session.requestAnimationFrame(onFrame);
};

document.querySelector('#enter-vr').addEventListener('click', startXR);

Controller-Position mit getPose() abfragen

const logControllers = (frame, refSpace) => {
  for (const source of frame.session.inputSources) {
    if (!source.gripSpace) continue;
    const pose = frame.getPose(source.gripSpace, refSpace);
    if (pose) {
      const { x, y, z } = pose.transform.position;
      console.log(`${source.handedness}: ${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)}`);
    }
  }
};

// Aufruf im Frame-Callback:
// session.requestAnimationFrame((t, frame) => logControllers(frame, refSpace));
left: -0.21, 1.05, -0.34 right: 0.24, 1.02, -0.31

Hit-Test-Ergebnisse in AR auswerten

const session = await navigator.xr.requestSession('immersive-ar', {
  requiredFeatures: ['hit-test']
});
const viewerSpace = await session.requestReferenceSpace('viewer');
const localSpace = await session.requestReferenceSpace('local');
const hitTestSource = await session.requestHitTestSource({ space: viewerSpace });

const onFrame = (time, frame) => {
  session.requestAnimationFrame(onFrame);
  const results = frame.getHitTestResults(hitTestSource);
  if (results.length > 0) {
    const hitPose = results[0].getPose(localSpace);
    console.log('Fläche bei', hitPose.transform.position);
  }
};
session.requestAnimationFrame(onFrame);
Fläche bei DOMPointReadOnly {x: 0.1, y: -0.8, z: -1.2, w: 1}

// Wichtig · Fallstricke

  • Nur im Callback gültig: Ein XRFrame ist nur während des Callbacks von requestAnimationFrame() aktiv. Ruft man getViewerPose() oder getPose() später auf, etwa in einem setTimeout oder nach einem await, wird eine InvalidStateError-DOMException ausgelöst. Das Objekt also nicht zwischenspeichern.
  • getViewerPose() ist nur für Frames aus requestAnimationFrame() erlaubt. Bei Frames aus Eingabe-Events (select, squeeze) ist nur getPose() zulässig.
  • Immer auf null prüfen, denn bei verlorenem Tracking gibt es keine Pose. pose.emulatedPosition zeigt an, ob die Position nur geschätzt ist.
  • Nicht window.requestAnimationFrame() verwenden, sondern XRSession.requestAnimationFrame(). Nur diese Variante liefert einen XRFrame und läuft mit der Bildrate des Headsets.
  • WebXR braucht einen sicheren Kontext (HTTPS) und eine Nutzeraktion zum Starten der Sitzung. Per Permissions-Policy (xr-spatial-tracking) lässt sich WebXR in iframes sperren.
  • Kompatibilität: Unterstützt in Chromium-Browsern (Chrome, Edge, Samsung Internet, Meta Quest Browser) sowie in Safari auf visionOS. Firefox und Desktop-Safari unterstützen WebXR nicht. Erweiterungen wie Hit Test, Anchors oder Hand Input gibt es nur mit dem passenden Feature-Deskriptor und je nach Gerät. Für die Entwicklung am Desktop hilft der WebXR API Emulator.