Signatur
Beschreibung
XRQuadLayer gehört zur WebXR Layers API und beschreibt eine flache, rechteckige Fläche (ein „Quad“) im virtuellen Raum. Der XR-Kompositor des Geräts setzt die Ebene direkt zusammen. Die Inhalte laufen also nicht durch die normale WebGL-Projektion. Deshalb erscheinen Text, Menüs, Bilder und Videos auf solchen Ebenen deutlich schärfer und mit weniger Verzerrung, weil das Bild nur einmal abgetastet wird.
Du erzeugst eine Instanz nicht mit einem Konstruktor, sondern über XRWebGLBinding.prototype.createQuadLayer(init). Für Videoinhalte gibt es XRMediaBinding.prototype.createQuadLayer(video, init). Damit die Ebene sichtbar wird, trägst du sie über XRSession.updateRenderState({ layers: [...] }) in die Liste der Ebenen der Sitzung ein.
Wichtige Eigenschaften:
space(XRSpace): Referenzraum, in dem die Ebene verankert ist.transform(XRRigidTransform): Position und Ausrichtung der Mitte der Ebene innerhalb vonspace.width/height(number): Größe der Ebene in Metern.- Von
XRCompositionLayergeerbt:layout,mipLevels,needsRedraw,blendTextureSourceAlpha,chromaticAberrationCorrection,qualityunddestroy(). - Ereignis
redraw: wird ausgelöst, wenn der Kompositor den Inhalt verloren hat und du ihn neu zeichnen musst.
Verwende XRQuadLayer für Inhalte, die im Raum stehen, aber flach bleiben sollen: virtuelle Monitore, HUD-Elemente, Untertitel oder Video-Leinwände. Für gekrümmte Flächen gibt es XRCylinderLayer, für Rundum-Hintergründe XREquirectLayer und XRCubeLayer.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $init.space Pflicht | XRSpace | Referenzraum, in dem die Ebene positioniert wird, z. B. ein local-Raum. |
|
| $init.viewPixelWidth Pflicht | number | Breite der Farbtextur in Pixeln (nur bei XRWebGLBinding). |
|
| $init.viewPixelHeight Pflicht | number | Höhe der Farbtextur in Pixeln (nur bei XRWebGLBinding). |
|
| $init.transform | XRRigidTransform | Identität | Position und Ausrichtung der Mitte der Ebene relativ zu space. |
| $init.width | number | 1.0 | Breite der Ebene in Metern. |
| $init.height | number | 1.0 | Höhe der Ebene in Metern. |
| $init.layout | "default" | "mono" | "stereo" | "stereo-left-right" | "stereo-top-bottom" | "mono" | Legt fest, ob beide Augen dasselbe Bild sehen oder jedes Auge ein eigenes. |
| $init.textureType | "texture" | "texture-array" | "texture" | Art der WebGL-Textur. texture-array erfordert WebGL2. |
| $init.colorFormat | GLenum | gl.RGBA | Farbformat der Textur. |
| $init.depthFormat | GLenum | 0 (keine Tiefe) | Optionales Format für den Tiefenpuffer. |
| $init.mipLevels | number | 1 | Anzahl der Mipmap-Stufen. |
| $init.isStatic | boolean | false | Bei true wird der Inhalt nur einmal gezeichnet und danach nur nach einem redraw-Ereignis. Das spart Rechenzeit bei statischen Inhalten. |
| $init.clearOnAccess | boolean | true | Legt fest, ob die Textur bei jedem Zugriff geleert wird. |
Rückgabewert
createQuadLayer() liefert eine neue XRQuadLayer-Instanz. Sie wird erst sichtbar, wenn du sie über updateRenderState({ layers }) in die Sitzung einträgst.Beispiele
Sitzung mit Layers-Feature starten und Quad-Ebene erzeugen
const startXR = async () => {
const session = await navigator.xr.requestSession('immersive-vr', {
requiredFeatures: ['layers']
});
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2', { xrCompatible: true });
const refSpace = await session.requestReferenceSpace('local');
const binding = new XRWebGLBinding(session, gl);
// Quad 1,5 m breit, 1 m hoch, 2 m vor dem Betrachter
const quadLayer = binding.createQuadLayer({
space: refSpace,
viewPixelWidth: 1536,
viewPixelHeight: 1024,
layout: 'mono',
width: 1.5,
height: 1.0,
transform: new XRRigidTransform({ x: 0, y: 1.5, z: -2 })
});
// Projektionsebene für die 3D-Szene plus Quad darüber
const projectionLayer = binding.createProjectionLayer();
session.updateRenderState({ layers: [projectionLayer, quadLayer] });
console.log(quadLayer.width, quadLayer.height);
return { session, gl, binding, quadLayer, refSpace };
};
Inhalt in die Quad-Textur zeichnen (Render-Schleife)
const { session, gl, binding, quadLayer } = await startXR();
const fb = gl.createFramebuffer();
const onFrame = (time, frame) => {
session.requestAnimationFrame(onFrame);
// Nur zeichnen, wenn nötig (z. B. beim ersten Frame oder nach redraw)
if (!quadLayer.needsRedraw) return;
const subImage = binding.getSubImage(quadLayer, frame);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, subImage.colorTexture, 0);
const { x, y, width, height } = subImage.viewport;
gl.viewport(x, y, width, height);
gl.clearColor(0.1, 0.4, 0.8, 1.0); // blaue Tafel
gl.clear(gl.COLOR_BUFFER_BIT);
};
session.requestAnimationFrame(onFrame);
Ebene verschieben, skalieren und auf redraw reagieren
// Virtuellen Monitor größer machen und nach rechts drehen
quadLayer.width = 2.4;
quadLayer.height = 1.35;
quadLayer.transform = new XRRigidTransform(
{ x: 0.5, y: 1.6, z: -2.5 },
{ x: 0, y: -0.259, z: 0, w: 0.966 } // ca. 30° um die Y-Achse
);
quadLayer.addEventListener('redraw', () => {
console.log('Kompositor verlangt ein Neuzeichnen');
});
// Aufräumen beim Beenden
session.addEventListener('end', () => quadLayer.destroy());
Video als Quad-Ebene über XRMediaBinding
const video = document.querySelector('video');
const mediaBinding = new XRMediaBinding(session);
const videoLayer = mediaBinding.createQuadLayer(video, {
space: refSpace,
layout: 'mono',
transform: new XRRigidTransform({ x: 0, y: 1.6, z: -3 })
});
session.updateRenderState({ layers: [projectionLayer, videoLayer] });
await video.play();