Signatur
Beschreibung
Das Ereignis visibilitymaskchange wird ausgelöst, wenn das XR-Gerät der Anwendung eine neue oder geänderte Sichtbarkeitsmaske für eine Ansicht (XRView) mitteilt. Bei vielen Headsets sieht der Benutzer wegen der Linsenform nicht den gesamten rechteckigen Framebuffer. Die Ränder oder Ecken sind also nie sichtbar. Die Maske beschreibt als Dreiecksnetz genau den Bereich, der tatsächlich sichtbar ist.
Das Ereignisobjekt ist ein XRVisibilityMaskChangeEvent. Es enthält die betroffene session, die zugehörige view, das Auge (eye), den Index der Ansicht (index) sowie die Geometrie als vertices (Float32Array) und indices (Uint32Array). Mit diesen Daten kann man vor dem eigentlichen Rendern zum Beispiel einen Stencil- oder Tiefenpuffer vorbelegen. Pixel außerhalb der Maske werden dann gar nicht erst schattiert, und das spart gerade bei aufwendigen Fragment-Shadern viel GPU-Zeit.
Den Handler registriert man entweder mit addEventListener("visibilitymaskchange", …) oder über die Eigenschaft XRSession.onvisibilitymaskchange. Das Ereignis kommt typischerweise kurz nach dem Start der Session (sobald das Gerät die Maske kennt) und erneut, wenn sich die Maske ändert, z. B. durch eine andere Linsen- oder Sichtfeldeinstellung. Weil die Maske nicht in jedem Frame mitgeliefert wird, sollte die Anwendung sie pro Ansicht zwischenspeichern.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignisname "visibilitymaskchange". |
|
| $handler Pflicht | (event: XRVisibilityMaskChangeEvent) => void | Callback, der das Ereignisobjekt erhält. Wichtige Eigenschaften: session (XRSession), view (XRView), eye ("left" | "right" | "none"), index (Nummer der Ansicht), vertices (Float32Array mit den Eckpunkt-Koordinaten der Maske) und indices (Uint32Array mit den Dreiecks-Indizes). |
|
| $options | boolean | AddEventListenerOptions | false | Die üblichen Optionen von addEventListener, z. B. { signal } zum späteren Abmelden über einen AbortController. |
Rückgabewert
addEventListener gibt nichts zurück. Die Daten stehen im übergebenen XRVisibilityMaskChangeEvent.Beispiele
Masken pro Ansicht mit addEventListener zwischenspeichern
const visibilityMasks = new Map();
const startXR = async () => {
const session = await navigator.xr.requestSession('immersive-vr');
session.addEventListener('visibilitymaskchange', (event) => {
const { eye, index, vertices, indices } = event;
// Kopien anlegen, damit die Daten sicher erhalten bleiben
visibilityMasks.set(index, {
eye,
vertices: new Float32Array(vertices),
indices: new Uint32Array(indices)
});
console.log(`Maske für Auge "${eye}" (Index ${index}): ${indices.length / 3} Dreiecke`);
});
return session;
};
document.querySelector('#enter-vr').addEventListener('click', startXR);
Maske in WebGL-Puffer laden (für Stencil-Vorpass)
const gl = document.querySelector('canvas').getContext('webgl2', { xrCompatible: true, stencil: true });
const maskBuffers = new Map();
const uploadMask = ({ index, vertices, indices }) => {
let entry = maskBuffers.get(index);
if (!entry) {
entry = { vbo: gl.createBuffer(), ibo: gl.createBuffer(), count: 0 };
maskBuffers.set(index, entry);
}
gl.bindBuffer(gl.ARRAY_BUFFER, entry.vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, entry.ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
entry.count = indices.length;
};
const session = await navigator.xr.requestSession('immersive-vr');
session.addEventListener('visibilitymaskchange', uploadMask);
// Im Frame-Loop pro Ansicht vor dem Szenen-Rendern:
// Stencil = 1 im sichtbaren Bereich schreiben, danach Szene nur mit
// gl.stencilFunc(gl.EQUAL, 1, 0xff) zeichnen.
// gl.drawElements(gl.TRIANGLES, entry.count, gl.UNSIGNED_INT, 0);
onvisibilitymaskchange-Eigenschaft und Abmelden
const session = await navigator.xr.requestSession('immersive-vr');
if ('onvisibilitymaskchange' in session) {
session.onvisibilitymaskchange = (event) => {
console.log('Neue Maske für', event.eye, event.vertices.length / 2, 'Koordinatenpaare');
};
} else {
console.warn('Sichtbarkeitsmasken werden nicht unterstützt – es wird der volle Framebuffer gerendert.');
}
// Beim Beenden aufräumen
session.addEventListener('end', () => {
session.onvisibilitymaskchange = null;
});
// Wichtig · Fallstricke
- Experimentell: Das Ereignis gehört zu einer neueren Erweiterung der WebXR Device API und wird derzeit nur von Chromium-basierten XR-Browsern unterstützt (u. a. Chrome, Meta Quest Browser). Firefox und Safari unterstützen es nicht. Prüfen Sie die Unterstützung mit
'onvisibilitymaskchange' in sessionund rendern Sie ohne Maske, wenn sie fehlt. - Nur in einem sicheren Kontext (HTTPS) und in immersiven Sessions sinnvoll. Nicht jedes Gerät liefert eine Maske. Ein ausbleibendes Ereignis bedeutet, dass die ganze Ansicht sichtbar ist.
- Die Maske ist nur eine Optimierung. Das Bild muss auch ohne sie korrekt aussehen. Rechnen Sie nicht damit, dass das Ereignis vor dem ersten Frame eintrifft.
- Verwenden Sie für die Zuordnung
indexbzw.eye, nicht die Objektidentität vonevent.view, dennXRView-Objekte werden in jedem Frame neu erzeugt. - Die Eckpunkte liegen im Koordinatenraum der jeweiligen Ansicht. Sie müssen im Shader mit der
projectionMatrixder Ansicht passend transformiert werden. Die genaue Konvention steht in der aktuellen Spezifikation. - Wenn die Daten asynchron weiterverwendet werden, kopieren Sie
verticesundindices, statt Verweise auf die Arrays des Ereignisses zu behalten.