Start · Sprachen · JavaScript · Referenz · XRWebGLSubImage

XRWebGLSubImage

Schnittstelle

Beschreibt die WebGL-Texturen (Farbe, Tiefe/Stencil, Bewegungsvektoren) und den Viewport, in die für eine WebXR-Schicht (<code>XRCompositionLayer</code>) in einem Frame gerendert wird.

seit JavaScript WebXR Layers API (experimentell) – Chromium-basier Kategorie: webapi

Signatur

interface XRWebGLSubImage extends XRSubImage

Beschreibung

XRWebGLSubImage gehört zur WebXR Layers API. Ein Objekt dieses Typs beschreibt für genau einen Frame, wohin du für eine Kompositionsschicht wie XRProjectionLayer, XRQuadLayer, XRCylinderLayer, XREquirectLayer oder XRCubeLayer rendern sollst. Es stellt die vom XR-Compositor verwalteten WebGL-Texturen bereit, die du an einen eigenen Framebuffer hängst.

Du erzeugst die Schnittstelle nie selbst (es gibt keinen Konstruktor). Stattdessen bekommst du sie über ein XRWebGLBinding:

  • binding.getViewSubImage(layer, view) – für Projektionsschichten, einmal pro XRView (also pro Auge).
  • binding.getSubImage(layer, frame, eye) – für Quad-, Zylinder-, Equirect- und Würfelschichten.

Wichtige Eigenschaften (alle schreibgeschützt):

  • colorTexture – WebGLTexture für die Farbwerte.
  • depthStencilTexture – WebGLTexture oder null, wenn die Schicht ohne Tiefenpuffer angelegt wurde.
  • motionVectorTexture – WebGLTexture oder null (für Space Warp / Reprojektion).
  • imageIndex – Ebenen-Index, wenn die Schicht als Texture-Array (textureType: "texture-array") angelegt wurde, sonst null.
  • colorTextureWidth, colorTextureHeight, depthStencilTextureWidth, depthStencilTextureHeight, motionVectorTextureWidth, motionVectorTextureHeight – Abmessungen der jeweiligen Texturen in Pixeln.
  • viewport (geerbt von XRSubImage) – ein XRViewport, der den Bereich innerhalb der Textur angibt, in den gerendert werden soll.

Die Texturen sind nur innerhalb des aktuellen requestAnimationFrame-Callbacks gültig. Hole das Sub-Image daher in jedem Frame neu und hänge die Texturen jedes Mal erneut an deinen Framebuffer.

Rückgabewert

Typ
XRWebGLSubImage
Beschreibung
Wird von XRWebGLBinding.getSubImage() und XRWebGLBinding.getViewSubImage() zurückgegeben; nur für den aktuellen Frame gültig.

Beispiele

Projektionsschicht anlegen und pro Auge rendern

const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2', { xrCompatible: true });

const session = await navigator.xr.requestSession('immersive-vr', {
  requiredFeatures: ['layers']
});
const refSpace = await session.requestReferenceSpace('local');

const binding = new XRWebGLBinding(session, gl);
const projLayer = binding.createProjectionLayer({ textureType: 'texture' });
session.updateRenderState({ layers: [projLayer] });

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) {
    // Sub-Image für dieses Auge holen
    const subImage = binding.getViewSubImage(projLayer, view);

    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D, subImage.colorTexture, 0);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT,
      gl.TEXTURE_2D, subImage.depthStencilTexture, 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 | gl.DEPTH_BUFFER_BIT);
    // drawScene(view.projectionMatrix, view.transform.inverse.matrix);
  }
};
session.requestAnimationFrame(onFrame);

Quad-Schicht (z. B. UI-Panel) mit getSubImage befüllen

const quadLayer = binding.createQuadLayer({
  space: refSpace,
  viewPixelWidth: 1024,
  viewPixelHeight: 512,
  width: 1.0,
  height: 0.5,
  transform: new XRRigidTransform({ x: 0, y: 1.5, z: -2 })
});
session.updateRenderState({ layers: [projLayer, quadLayer] });

const drawQuad = (frame) => {
  // Inhalt nur neu zeichnen, wenn der Compositor es verlangt
  if (!quadLayer.needsRedraw) return;

  const subImage = binding.getSubImage(quadLayer, frame);
  gl.bindTexture(gl.TEXTURE_2D, subImage.colorTexture);
  // z. B. ein 2D-Canvas mit UI als Textur hochladen
  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0,
    gl.RGBA, gl.UNSIGNED_BYTE, uiCanvas);
  console.log(subImage.colorTextureWidth, subImage.colorTextureHeight);
};
1024 512

Texture-Array: imageIndex als Ebene verwenden

const arrayLayer = binding.createProjectionLayer({
  textureType: 'texture-array'
});
session.updateRenderState({ layers: [arrayLayer] });

const renderView = (view) => {
  const subImage = binding.getViewSubImage(arrayLayer, view);
  // Bei Texture-Arrays zeigt imageIndex die Ebene des Auges an
  gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
    subImage.colorTexture, 0, subImage.imageIndex);
  console.log(`Auge ${view.eye} -> Ebene ${subImage.imageIndex}`);
};
Auge left -> Ebene 0 Auge right -> Ebene 1

// Wichtig · Fallstricke

  • Experimentell: Die WebXR Layers API wird derzeit nur von wenigen Browsern unterstützt (vor allem Meta Quest Browser und Chromium mit XR-Unterstützung). Firefox und Safari bieten sie nicht an. Fordere 'layers' als Feature an und sieh eine Ausweichlösung über XRWebGLLayer vor.
  • Der WebGL-Kontext muss mit xrCompatible: true angelegt werden oder über gl.makeXRCompatible() kompatibel gemacht werden. Texture-Arrays und framebufferTextureLayer() setzen WebGL 2 voraus.
  • Die Texturen gehören dem Compositor: Ruf kein gl.deleteTexture(), texImage2D() oder texStorage2D() auf ihnen auf und speichere sie nicht über mehrere Frames hinweg. Außerhalb des Frame-Callbacks sind sie ungültig.
  • Außerhalb eines aktiven Animation-Frames oder bei falschem Schichttyp werfen getSubImage() bzw. getViewSubImage() einen InvalidStateError.
  • Rendere nur in den Bereich, den viewport angibt. Die Textur kann größer sein als der Viewport, etwa wenn beide Augen nebeneinander in derselben Textur liegen.
  • depthStencilTexture und motionVectorTexture können null sein. Prüfe das, bevor du sie an den Framebuffer hängst.