Signatur
Beschreibung
XRSubImage gehört zum Modul WebXR Layers der WebXR Device API. Ein Objekt dieser Schnittstelle gibt an, welchen Ausschnitt einer Textur die Anwendung für eine bestimmte Ebene (z. B. XRProjectionLayer oder XRQuadLayer) und ein bestimmtes Auge bemalen soll. Die Schnittstelle hat nur eine Eigenschaft: viewport vom Typ XRViewport mit x, y, width und height in Pixeln.
Direkt benutzt man XRSubImage in der Praxis kaum. Man bekommt fast immer die abgeleitete Schnittstelle XRWebGLSubImage. Sie ergänzt unter anderem colorTexture, depthStencilTexture, imageIndex, textureWidth und textureHeight. Solche Objekte liefern die Methoden XRWebGLBinding.getSubImage(layer, frame, eye) und XRWebGLBinding.getViewSubImage(layer, view).
Man braucht XRSubImage immer dann, wenn man mit Ebenen statt mit dem klassischen XRWebGLLayer rendert. In jedem Frame holt man sich für jede Ansicht das passende Sub-Image, bindet dessen Textur an einen Framebuffer und setzt mit gl.viewport() den Ausschnitt aus subImage.viewport. Mehrere Augen können sich eine Textur teilen, etwa bei layout: "stereo-left-right". Deshalb darf man den Viewport nie selbst ausrechnen, sondern muss immer den gelieferten Wert verwenden.
Rückgabewert
viewport (schreibgeschützt) liefert ein XRViewport mit x, y, width und height. Diese Werte gibt man direkt an gl.viewport() weiter.Beispiele
Projektionsebene: Sub-Image pro Auge holen und Viewport setzen
const session = await navigator.xr.requestSession("immersive-vr", {
requiredFeatures: ["layers"]
});
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl2", { xrCompatible: true });
const xrGlBinding = new XRWebGLBinding(session, gl);
const projectionLayer = xrGlBinding.createProjectionLayer({ textureType: "texture" });
session.updateRenderState({ layers: [projectionLayer] });
const refSpace = await session.requestReferenceSpace("local");
const framebuffer = gl.createFramebuffer();
const onXRFrame = (time, frame) => {
session.requestAnimationFrame(onXRFrame);
const pose = frame.getViewerPose(refSpace);
if (!pose) return;
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
for (const view of pose.views) {
// XRWebGLSubImage erbt von XRSubImage
const subImage = xrGlBinding.getViewSubImage(projectionLayer, view);
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.2, 0.3, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// ... Szene für view.projectionMatrix / view.transform zeichnen
}
};
session.requestAnimationFrame(onXRFrame);
Quad-Ebene: Sub-Image mit getSubImage() nur bei Bedarf neu zeichnen
const refSpace = await session.requestReferenceSpace("local");
const quadLayer = xrGlBinding.createQuadLayer({
space: refSpace,
viewPixelWidth: 512,
viewPixelHeight: 512,
layout: "mono"
});
quadLayer.transform = new XRRigidTransform({ x: 0, y: 1.5, z: -2 });
quadLayer.width = 1;
quadLayer.height = 1;
session.updateRenderState({ layers: [projectionLayer, quadLayer] });
const drawQuad = (frame) => {
if (!quadLayer.needsRedraw) return; // Inhalt ist noch gültig
const subImage = xrGlBinding.getSubImage(quadLayer, frame);
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, subImage.colorTexture, 0);
const vp = subImage.viewport;
gl.viewport(vp.x, vp.y, vp.width, vp.height);
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// ... UI-Inhalt in die Quad-Textur zeichnen
};
Viewport eines Sub-Images zur Fehlersuche ausgeben
const logSubImage = (subImage, label) => {
console.log(subImage instanceof XRSubImage); // true, auch für XRWebGLSubImage
const { x, y, width, height } = subImage.viewport;
console.log(`${label}: x=${x}, y=${y}, ${width}x${height}`);
};
// Im Frame-Callback:
for (const view of pose.views) {
logSubImage(xrGlBinding.getViewSubImage(projectionLayer, view), view.eye);
}