Start · Sprachen · JavaScript · Referenz · XRSubImage.viewport

XRSubImage.viewport

Eigenschaft

Schreibgeschützte Eigenschaft, die den <code>XRViewport</code> liefert, also den Pixelbereich der Textur, in den ein Teilbild einer WebXR-Ebene gerendert wird.

seit JavaScript Experimentell: WebXR Layers API (Chromium-basierte Kategorie: webapi

Signatur

XRSubImage.viewport

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

Typ
XRViewport
Beschreibung
Ein Objekt mit den schreibgeschützten Eigenschaften 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})`);
};
Viewport: 1024x512 bei (0, 0)

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 });
  }
};
left { x: 0, y: 0, width: 800, height: 600 } right { x: 800, y: 0, width: 800, height: 600 }

// 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 bei requestSession() 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 sich x und y. Beim Löschen der Textur mit gl.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).