Start · Sprachen · JavaScript · Referenz · XRWebGLBinding.getSubImage()

XRWebGLBinding.getSubImage()

Methode

Liefert für eine WebXR-Kompositionsebene und den aktuellen Frame ein <code>XRWebGLSubImage</code> mit der WebGL-Textur und dem Viewport, in den gerendert werden soll.

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

Signatur

XRWebGLBinding.prototype.getSubImage(layer, frame, eye)

Beschreibung

Die Methode getSubImage() gehört zur WebXR Layers API. Sie gibt für eine Ebene (Layer) wie XRQuadLayer, XRCylinderLayer, XREquirectLayer oder XRCubeLayer ein XRWebGLSubImage-Objekt zurück. Dieses enthält die Farbtextur (colorTexture), optional eine Tiefen-/Stencil-Textur (depthStencilTexture), den Index in einem Textur-Array (imageIndex), die Texturgröße (textureWidth, textureHeight) sowie den viewport, in den Ihr WebGL-Code zeichnen soll.

Man ruft sie in jedem Animations-Frame innerhalb des Callbacks von XRSession.requestAnimationFrame() auf, weil die Laufzeitumgebung die Texturen einer Swapchain zwischen Frames wechseln kann. Die zurückgegebene Textur hängt man an einen eigenen Framebuffer, setzt den Viewport und rendert den Inhalt der Ebene – etwa ein UI-Panel, ein Video oder eine Textanzeige, die der Kompositor schärfer darstellt als eine normale Projektionsebene.

Für XRProjectionLayer ist stattdessen getViewSubImage(layer, view) zu verwenden, da dort pro XRView gerendert wird. Bei Ebenen mit dem Layout "stereo" bzw. "stereo-left-right"/"stereo-top-bottom" gibt der Parameter eye an, für welches Auge das Teilbild abgefragt wird.

Parameter

Name Typ Default Beschreibung
$layer Pflicht XRCompositionLayer — Die Ebene, für die das Teilbild abgefragt wird – typischerweise ein XRQuadLayer, XRCylinderLayer, XREquirectLayer oder XRCubeLayer, das mit derselben XRWebGLBinding-Instanz erzeugt wurde.
$frame Pflicht XRFrame — Der aktuelle, aktive XRFrame aus dem Callback von XRSession.requestAnimationFrame().
$eye "left" | "right" | "none" "none" Auge, für das das Teilbild geliefert wird. Nur bei Stereo-Layouts relevant; bei "mono"-Ebenen "none" verwenden oder weglassen.

Rückgabewert

Typ
XRWebGLSubImage
Beschreibung
Objekt mit colorTexture (WebGLTexture), depthStencilTexture (WebGLTexture | null), imageIndex (number | null, Ebene im Textur-Array), textureWidth, textureHeight und viewport (XRViewport).

Beispiele

Quad-Ebene erzeugen und pro Frame 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);

// Flaches UI-Panel 1 m vor dem Nutzer
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: -1 })
});
session.updateRenderState({ layers: [quadLayer] });

const fb = gl.createFramebuffer();

const onFrame = (time, frame) => {
  session.requestAnimationFrame(onFrame);

  // Nur neu zeichnen, wenn der Inhalt verloren ging bzw. neu benötigt wird
  if (!quadLayer.needsRedraw) return;

  const subImage = binding.getSubImage(quadLayer, frame);
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  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.4, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
};
session.requestAnimationFrame(onFrame);

Stereo-Ebene: Teilbild pro Auge abfragen

const stereoLayer = binding.createQuadLayer({
  space: refSpace,
  layout: 'stereo-left-right',
  viewPixelWidth: 1024,
  viewPixelHeight: 1024,
  width: 1.6,
  height: 0.9
});

const drawStereo = (frame) => {
  for (const eye of ['left', 'right']) {
    const sub = binding.getSubImage(stereoLayer, frame, eye);
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D, sub.colorTexture, 0);
    const vp = sub.viewport;
    gl.viewport(vp.x, vp.y, vp.width, vp.height);
    console.log(eye, vp.x, vp.width);
    // ... Szene für dieses Auge zeichnen
  }
};
left 0 1024 right 1024 1024

Textur-Array mit imageIndex berücksichtigen

const sub = binding.getSubImage(layer, frame, 'left');
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

if (sub.imageIndex !== null) {
  // Ebene wurde mit textureType: 'texture-array' angelegt
  gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
    sub.colorTexture, 0, sub.imageIndex);
} else {
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
    gl.TEXTURE_2D, sub.colorTexture, 0);
}