Signatur
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: dieXRSession, 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 eineXRViewerPosemit den Blickpunkten (views) des Nutzers, zum Beispiel ein Blickpunkt pro Auge. Kannnullsein, wenn das Tracking verloren ist.frame.getPose(space, baseSpace): liefert dieXRPoseeines beliebigenXRSpace(etwainputSource.gripSpaceodertargetRaySpace) 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
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));
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);
// Wichtig · Fallstricke
- Nur im Callback gültig: Ein
XRFrameist nur während des Callbacks vonrequestAnimationFrame()aktiv. Ruft mangetViewerPose()odergetPose()später auf, etwa in einemsetTimeoutoder nach einemawait, wird eineInvalidStateError-DOMExceptionausgelöst. Das Objekt also nicht zwischenspeichern. getViewerPose()ist nur für Frames ausrequestAnimationFrame()erlaubt. Bei Frames aus Eingabe-Events (select,squeeze) ist nurgetPose()zulässig.- Immer auf
nullprüfen, denn bei verlorenem Tracking gibt es keine Pose.pose.emulatedPositionzeigt an, ob die Position nur geschätzt ist. - Nicht
window.requestAnimationFrame()verwenden, sondernXRSession.requestAnimationFrame(). Nur diese Variante liefert einenXRFrameund 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.