Start · Sprachen · JavaScript · Referenz · XRWebGLLayer

XRWebGLLayer

Schnittstelle

Verbindet eine WebXR-Sitzung mit einem WebGL-Kontext und stellt den Framebuffer bereit, in den die XR-Szene für jedes Auge bzw. jede Ansicht gerendert wird.

seit JavaScript WebXR Device API – Chromium-Browser (Chrome/Edge 7 Kategorie: webapi

Signatur

interface XRWebGLLayer extends XRLayer

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 den XRViewport fü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

Typ
XRWebGLLayer
Beschreibung
Eine neue Ebene, die per 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);
Framebuffer: 3664x1920

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;
}
Nativer Skalierungsfaktor: 1.4

// 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.xr und isSessionSupported().
  • 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ätzlich allow="xr-spatial-tracking".
  • Framebuffer nicht löschen oder umkonfigurieren: layer.framebuffer gehört dem Compositor. gl.deleteFramebuffer(), framebufferTexture2D() oder das Abfragen von Attachments darauf führen zu INVALID_OPERATION. Außerdem ist er nur innerhalb des XR-requestAnimationFrame-Callbacks gültig.
  • Nicht das Canvas-rAF verwenden: Nutze session.requestAnimationFrame(), nicht window.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 neue XRWebGLLayer anlegen.
  • Leistung: Ein hoher framebufferScaleFactor vervielfacht die Pixellast. Auf mobilen Headsets lohnt es sich, unter 1.0 zu bleiben oder fixedFoveation zu nutzen.
  • Du kannst die Ebene jederzeit mit einer neuen Instanz über updateRenderState() ersetzen. Die Änderung wird ab dem nächsten Frame wirksam.