Signatur
Beschreibung
XRWebGLLayer ist die zentrale Schnittstelle der WebXR Device API, über die eine XRSession mit einem WebGLRenderingContext oder WebGL2RenderingContext verknüpft wird. Die Ebene (Layer) liefert einen eigenen, vom XR-Gerät verwalteten WebGLFramebuffer. Alles, was die Anwendung in diesen Framebuffer zeichnet, wird vom Browser an das Headset bzw. an die Inline-Darstellung weitergegeben.
Man erzeugt die Ebene mit new XRWebGLLayer(session, gl, options) und übergibt sie anschließend per session.updateRenderState({ baseLayer: layer }) an die Sitzung. In jedem Durchlauf von requestAnimationFrame() bindet man layer.framebuffer und ermittelt mit layer.getViewport(view) für jede XRView (z. B. linkes und rechtes Auge) den Bildausschnitt, in den gezeichnet werden soll.
Wichtige Eigenschaften und Methoden:
antialias(nur lesbar,boolean): ob der Framebuffer Kantenglättung unterstützt.framebuffer(nur lesbar,WebGLFramebuffer | null): Ziel-Framebuffer; bei Inline-Sitzungen ggf.null(dann wird in den Standard-Framebuffer des Canvas gezeichnet).framebufferWidth/framebufferHeight(nur lesbar,number): Größe des Framebuffers in Pixeln.ignoreDepthValues(nur lesbar,boolean): ob der Compositor den Tiefenpuffer ignoriert.fixedFoveation(number | null): Stärke des Fixed Foveated Rendering (0–1), sofern unterstützt.getViewport(view): liefert denXRViewportfür eine Ansicht.XRWebGLLayer.getNativeFramebufferScaleFactor(session)(statisch): Skalierungsfaktor, bei dem ein Pixel des Framebuffers einem physischen Displaypixel entspricht.
Der WebGL-Kontext muss XR-kompatibel sein – entweder beim Erzeugen per { xrCompatible: true } oder nachträglich über await gl.makeXRCompatible(). Für fortgeschrittene Szenarien (mehrere Ebenen, Quad-/Zylinder-Layer) gibt es die separate WebXR Layers API mit XRWebGLBinding.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $session Pflicht | XRSession | Die aktive XR-Sitzung, an die die Ebene gebunden wird. Ist sie bereits beendet, wird ein InvalidStateError geworfen. |
|
| $context Pflicht | WebGLRenderingContext | WebGL2RenderingContext | Der WebGL-Kontext zum Rendern. Muss XR-kompatibel sein (xrCompatible: true oder makeXRCompatible()), sonst InvalidStateError. |
|
| $options | XRWebGLLayerInit | {} | Optionales Objekt: antialias (Standard true), depth (Standard true), stencil (Standard false), alpha (Standard true), ignoreDepthValues (Standard false) und framebufferScaleFactor (Standard 1.0) zur Steuerung der Auflösung. |
Rückgabewert
session.updateRenderState({ baseLayer }) als Render-Ziel der Sitzung gesetzt wird.Beispiele
Immersive VR-Sitzung starten und XRWebGLLayer anlegen
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2', { xrCompatible: true });
const startVR = async () => {
if (!navigator.xr || !(await navigator.xr.isSessionSupported('immersive-vr'))) {
console.log('immersive-vr wird nicht unterstützt');
return;
}
const session = await navigator.xr.requestSession('immersive-vr');
const layer = new XRWebGLLayer(session, gl, { antialias: true, alpha: false });
await session.updateRenderState({ baseLayer: layer });
console.log(`Framebuffer: ${layer.framebufferWidth}x${layer.framebufferHeight}`);
};
// Muss durch eine Nutzergeste ausgelöst werden
document.querySelector('#enter-vr').addEventListener('click', startVR);
Render-Schleife: pro Auge in den richtigen Viewport zeichnen
const onXRFrame = (time, frame) => {
const session = frame.session;
session.requestAnimationFrame(onXRFrame);
const pose = frame.getViewerPose(refSpace);
if (!pose) return;
const layer = session.renderState.baseLayer;
gl.bindFramebuffer(gl.FRAMEBUFFER, layer.framebuffer);
gl.clearColor(0.1, 0.1, 0.15, 1);
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);
// eigene Zeichenfunktion mit Projektions- und View-Matrix
drawScene(view.projectionMatrix, view.transform.inverse.matrix);
}
};
const refSpace = await session.requestReferenceSpace('local-floor');
session.requestAnimationFrame(onXRFrame);
Auflösung an native Displaypixel anpassen
const session = await navigator.xr.requestSession('immersive-vr');
await gl.makeXRCompatible();
const nativeScale = XRWebGLLayer.getNativeFramebufferScaleFactor(session);
console.log('Nativer Skalierungsfaktor:', nativeScale);
// Kompromiss aus Schärfe und Leistung: 75 % der nativen Auflösung
const layer = new XRWebGLLayer(session, gl, {
framebufferScaleFactor: nativeScale * 0.75
});
await session.updateRenderState({ baseLayer: layer });
// Fixed Foveation aktivieren, falls verfügbar (z. B. Meta Quest)
if (layer.fixedFoveation !== undefined) {
layer.fixedFoveation = 1.0;
}
// Wichtig · Fallstricke
- Browser-Unterstützung: WebXR gibt es nur in Chromium-Browsern (Chrome, Edge, Samsung Internet), im Meta Quest Browser und in Safari auf visionOS. Firefox Desktop und Safari auf macOS/iOS unterstützen es nicht. Prüfe deshalb immer
navigator.xrundisSessionSupported(). - Sicherer Kontext: Die API funktioniert nur über HTTPS (oder
localhost). Immersive Sitzungen müssen durch eine Nutzergeste ausgelöst werden. In iframes braucht es zusätzlichallow="xr-spatial-tracking". - Framebuffer nicht löschen oder umkonfigurieren:
layer.framebuffergehört dem Compositor.gl.deleteFramebuffer(),framebufferTexture2D()oder das Abfragen von Attachments darauf führen zuINVALID_OPERATION. Außerdem ist er nur innerhalb des XR-requestAnimationFrame-Callbacks gültig. - Nicht das Canvas-rAF verwenden: Nutze
session.requestAnimationFrame(), nichtwindow.requestAnimationFrame(). Headsets arbeiten oft mit 72–120 Hz. - Kontextverlust: Geht der WebGL-Kontext verloren (
webglcontextlost), ist die Ebene unbrauchbar. Nach der Wiederherstellung musst du eine neueXRWebGLLayeranlegen. - Leistung: Ein hoher
framebufferScaleFactorvervielfacht die Pixellast. Auf mobilen Headsets lohnt es sich, unter 1.0 zu bleiben oderfixedFoveationzu nutzen. - Du kannst die Ebene jederzeit mit einer neuen Instanz über
updateRenderState()ersetzen. Die Änderung wird ab dem nächsten Frame wirksam.