Signatur
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 proXRView(also pro Auge).binding.getSubImage(layer, frame, eye)– für Quad-, Zylinder-, Equirect- und Würfelschichten.
Wichtige Eigenschaften (alle schreibgeschützt):
colorTexture–WebGLTexturefür die Farbwerte.depthStencilTexture–WebGLTextureodernull, wenn die Schicht ohne Tiefenpuffer angelegt wurde.motionVectorTexture–WebGLTextureodernull(für Space Warp / Reprojektion).imageIndex– Ebenen-Index, wenn die Schicht als Texture-Array (textureType: "texture-array") angelegt wurde, sonstnull.colorTextureWidth,colorTextureHeight,depthStencilTextureWidth,depthStencilTextureHeight,motionVectorTextureWidth,motionVectorTextureHeight– Abmessungen der jeweiligen Texturen in Pixeln.viewport(geerbt vonXRSubImage) – einXRViewport, 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
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);
};
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}`);
};
// 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 überXRWebGLLayervor. - Der WebGL-Kontext muss mit
xrCompatible: trueangelegt werden oder übergl.makeXRCompatible()kompatibel gemacht werden. Texture-Arrays undframebufferTextureLayer()setzen WebGL 2 voraus. - Die Texturen gehören dem Compositor: Ruf kein
gl.deleteTexture(),texImage2D()odertexStorage2D()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()einenInvalidStateError. - Rendere nur in den Bereich, den
viewportangibt. Die Textur kann größer sein als der Viewport, etwa wenn beide Augen nebeneinander in derselben Textur liegen. depthStencilTextureundmotionVectorTexturekönnennullsein. Prüfe das, bevor du sie an den Framebuffer hängst.