Signatur
Beschreibung
Die Eigenschaft viewport der Schnittstelle XRSubImage gibt ein XRViewport-Objekt zurück. Es beschreibt mit x, y, width und height den rechteckigen Bereich einer Ebenen-Textur, den du für ein bestimmtes Teilbild beschreiben sollst. Die Werte sind Pixel. Die Koordinaten folgen der WebGL-Konvention, der Ursprung liegt also unten links.
In der Praxis bekommst du meist ein XRWebGLSubImage, das von XRSubImage erbt. Du erhältst es innerhalb des Frame-Callbacks über XRWebGLBinding.getViewSubImage(layer, view) (für Projektionsebenen, pro Auge) oder über XRWebGLBinding.getSubImage(layer, frame, eye) (für Quad-, Zylinder-, Würfel- und Equirect-Ebenen). Die Werte von viewport übergibst du direkt an gl.viewport() und bei Bedarf an gl.scissor().
Das ist wichtig, weil sich mehrere Ansichten eine Textur teilen können. Beim Layout "stereo-left-right" liegen zum Beispiel beide Augen nebeneinander in einer Textur. Nur der Viewport sagt dir, welcher Teil zu welchem Auge gehört. Außerdem kann der Browser die Auflösung dynamisch anpassen, deshalb sollst du den Viewport in jedem Frame neu auslesen.
Rückgabewert
x, y, width und height (in Pixeln). Sie beschreiben den Zielbereich der Ebenen-Textur für dieses Teilbild.Beispiele
Projektionsebene: Viewport pro Auge setzen
const session = await navigator.xr.requestSession("immersive-vr", {
requiredFeatures: ["layers", "local-floor"]
});
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl2", { xrCompatible: true });
const glBinding = new XRWebGLBinding(session, gl);
const layer = glBinding.createProjectionLayer({ textureType: "texture" });
session.updateRenderState({ layers: [layer] });
const refSpace = await session.requestReferenceSpace("local-floor");
const fb = gl.createFramebuffer();
const onFrame = (time, frame) => {
session.requestAnimationFrame(onFrame);
const pose = frame.getViewerPose(refSpace);
if (!pose) return;
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (const view of pose.views) {
const subImage = glBinding.getViewSubImage(layer, 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);
// drawScene(view) – hier die eigene Szene zeichnen
}
};
session.requestAnimationFrame(onFrame);
Quad-Ebene: Viewport auslesen und Scissor-Test nutzen
const quadLayer = glBinding.createQuadLayer({
space: refSpace,
viewPixelWidth: 1024,
viewPixelHeight: 512,
layout: "mono"
});
session.updateRenderState({ layers: [quadLayer] });
const renderQuad = (frame) => {
if (!quadLayer.needsRedraw) return;
const subImage = glBinding.getSubImage(quadLayer, frame);
const vp = subImage.viewport;
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, subImage.colorTexture, 0);
gl.viewport(vp.x, vp.y, vp.width, vp.height);
gl.enable(gl.SCISSOR_TEST);
gl.scissor(vp.x, vp.y, vp.width, vp.height);
gl.clearColor(0.1, 0.1, 0.3, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
console.log(`Viewport: ${vp.width}x${vp.height} bei (${vp.x}, ${vp.y})`);
};
Stereo-Layout: linkes und rechtes Teilbild vergleichen
const stereoLayer = glBinding.createQuadLayer({
space: refSpace,
viewPixelWidth: 800,
viewPixelHeight: 600,
layout: "stereo-left-right"
});
const logViewports = (frame) => {
for (const eye of ["left", "right"]) {
const { x, y, width, height } = glBinding.getSubImage(stereoLayer, frame, eye).viewport;
console.log(eye, { x, y, width, height });
}
};
// Wichtig · Fallstricke
- Experimentell: Die Eigenschaft gehört zur WebXR Layers API. Chromium-basierte Browser mit XR-Hardware und der Meta Quest Browser unterstützen sie. Firefox und Safari unterstützen sie nicht. Die Funktion
"layers"muss beirequestSession()angefordert werden. Außerdem ist ein sicherer Kontext (HTTPS) nötig. - Teilbilder und ihre Viewports gelten nur im aktuellen
requestAnimationFrame-Callback der Sitzung. Lies sie in jedem Frame neu aus und speichere sie nicht zwischen, denn der Browser kann die Auflösung dynamisch ändern. - Geh nicht davon aus, dass der Viewport bei
(0, 0)beginnt. Bei Stereo-Layouts oder gemeinsam genutzten Texturen verschieben sichxundy. Beim Löschen der Textur mitgl.clear()solltest du deshalb den Scissor-Test verwenden, sonst überschreibst du die andere Ansicht. - Die angegebenen Beispielausgaben für das Stereo-Layout sind typisch. Die tatsächliche Aufteilung legt aber der Browser fest.
- Ohne Layers API nutzt du stattdessen
XRWebGLLayer.getViewport(view).