Signatur
Beschreibung
Die schreibgeschützte Eigenschaft projectionMatrix der Schnittstelle XRView gibt die Projektionsmatrix zurück, die für die jeweilige Ansicht verwendet werden muss. Es handelt sich um ein Float32Array mit 16 Werten in spaltenweiser Anordnung (column-major). Das Format passt direkt zu WebGL-Funktionen wie gl.uniformMatrix4fv() und zu Bibliotheken wie gl-matrix oder three.js.
Die Matrix beschreibt Sichtfeld (field of view), Seitenverhältnis sowie die Near- und Far-Clipping-Ebenen der Ansicht. Bei VR-Headsets ist sie meist asymmetrisch (off-axis), weil jedes Auge einen anderen Frustum hat. Deshalb sollte man sie nie selbst berechnen (etwa mit mat4.perspective()), sondern immer den vom Gerät gelieferten Wert verwenden. Sonst entstehen Verzerrungen, Doppelbilder und im schlimmsten Fall Übelkeit beim Nutzer.
Man verwendet die Eigenschaft in jedem Frame innerhalb des Callbacks von XRSession.requestAnimationFrame(). Dort liefert XRFrame.getViewerPose() ein XRViewerPose-Objekt, dessen Array views für jede Ansicht ein XRView enthält. Die Projektionsmatrix wird zusammen mit der View-Matrix (view.transform.inverse.matrix) an den Shader übergeben.
Die Near- und Far-Werte der Matrix richten sich nach XRRenderState.depthNear und depthFar. Diese lassen sich über XRSession.updateRenderState() ändern. Die Matrix kann sich deshalb von Frame zu Frame ändern.
Rückgabewert
Float32Array mit 16 Elementen: eine 4×4-Projektionsmatrix in spaltenweiser Anordnung für die jeweilige Ansicht. Ist die Ansicht ungültig, wird beim Zugriff eine InvalidStateError-Exception geworfen.Beispiele
Projektionsmatrix pro Auge an einen WebGL-Shader übergeben
// Voraussetzung: session, refSpace, gl, program und glLayer sind bereits eingerichtet
const uProjection = gl.getUniformLocation(program, 'uProjectionMatrix');
const uView = gl.getUniformLocation(program, 'uViewMatrix');
const onXRFrame = (time, frame) => {
const session = frame.session;
session.requestAnimationFrame(onXRFrame);
const pose = frame.getViewerPose(refSpace);
if (!pose) return;
const glLayer = session.renderState.baseLayer;
gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
for (const view of pose.views) {
const vp = glLayer.getViewport(view);
gl.viewport(vp.x, vp.y, vp.width, vp.height);
// Direkt verwendbar, da column-major Float32Array
gl.uniformMatrix4fv(uProjection, false, view.projectionMatrix);
gl.uniformMatrix4fv(uView, false, view.transform.inverse.matrix);
drawScene();
}
};
session.requestAnimationFrame(onXRFrame);
Matrix inspizieren und Near/Far-Ebene zurückrechnen
const logProjection = (view) => {
const m = view.projectionMatrix;
console.log(view.eye, m.length);
// Für eine Standard-Perspektivmatrix: m[10] = (f+n)/(n-f), m[14] = 2fn/(n-f)
const near = m[14] / (m[10] - 1);
const far = m[14] / (m[10] + 1);
console.log(`near=${near.toFixed(2)} far=${far.toFixed(2)}`);
};
session.requestAnimationFrame((t, frame) => {
const pose = frame.getViewerPose(refSpace);
pose?.views.forEach(logProjection);
});
Clipping-Ebenen ändern und Matrix mit three.js nutzen
import * as THREE from 'three';
// Near/Far beeinflussen die gelieferte projectionMatrix
await session.updateRenderState({ depthNear: 0.05, depthFar: 500 });
const camera = new THREE.PerspectiveCamera();
const applyView = (view) => {
camera.projectionMatrix.fromArray(view.projectionMatrix);
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
camera.matrixWorld.fromArray(view.transform.matrix);
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
};
// Wichtig · Fallstricke
- Nicht selbst berechnen: Die Matrizen der Headsets sind oft asymmetrisch.
mat4.perspective()oder ein festes Sichtfeld führen zu fehlerhafter Stereodarstellung. - Pro Frame neu lesen: Die Matrix kann sich ändern, zum Beispiel nach
updateRenderState()oder bei Kalibrierung des Geräts. Deshalb weder zwischenspeichern noch über mehrere Frames wiederverwenden. - Nur im gültigen Frame:
XRView-Objekte sind nur während des zugehörigenrequestAnimationFrame-Callbacks aktiv. Ein späterer Zugriff kann eineInvalidStateError-Exception auslösen. - Spaltenweise Anordnung: Beim Übergeben an WebGL
transposeauffalsesetzen. In DirectX-artigen Mathematikbibliotheken ist gegebenenfalls eine Transposition nötig. - Kompatibilität: WebXR erfordert einen sicheren Kontext (HTTPS). Unterstützt wird es in Chromium-Browsern (Chrome, Edge, Samsung Internet, Meta Quest Browser). Firefox und Safari auf dem Desktop unterstützen die API nicht, Safari auf der Apple Vision Pro nur eingeschränkt. Die Verfügbarkeit per
navigator.xr?.isSessionSupported()prüfen.