WebGL 画布中的 HTML

在 WebGL 画布中渲染 HTML,可让您将实时 DOM 子树用作动态 GPU 纹理,将着色器效果应用于实时 DOM,并将交互式 HTML 集成到 3D 场景中。

本指南演示了如何将 HTML 渲染到 WebGL 画布中。如需详细了解 HTML-in-Canvas,请参阅 API 概览。

在 WebGL 画布中呈现 HTML 需要执行以下步骤:

  1. 准备画布。以声明方式设置正确的属性,让浏览器知道它应该将 HTML 渲染到画布中。此设置在 2D、WebGL 和 WebGPU 上下文中完全相同。了解如何设置画布以及如何声明要独立绘制的 HTML 元素。
  2. 将 HTML 作为纹理应用:
    1. 预分配 GPU 纹理存储空间。
    2. 将 DOM 快照作为纹理上传。
  3. 同步 DOM 和像素。

将 HTML 元素绘制为 WebGL 纹理

在 WebGL 中,HTML 元素会使用 texElementSubImage2D() 作为 2D 纹理上传到 GPU。

预分配纹理存储空间

与自动管理后备内存的 2D 画布不同,WebGL 上下文需要显式内存管理。

在上传 DOM 像素之前,您必须预先分配 GPU 内存。这样,浏览器就可以执行快速子图片更新,而不会触发昂贵的 GPU 内存重新分配。

使用所选的大小参数预分配缓冲区,并将 srcData 设置为 null,如下所示:

// Only allocate texture backing once
if (!textureBufferAllocated) {
  // Pre-allocate texture backing with pre-determined size.
  gl.texImage2D(
    /* target */ gl.TEXTURE_2D,
    /* level */ 0,
    /* internalformat */ gl.RGBA8,
    /* width */ myElementWidth,
    /* height */ myElementHeight,
    /* border */ 0,
    /* format */ gl.RGBA,
    /* type */ gl.UNSIGNED_BYTE,
    /* srcData */ null
  );

  textureBufferAllocated = true;
}

如需使纹理与 HTML 元素的自然布局大小相匹配,请使用 canvas.getElementImageDefaultSize() 检索其默认大小(以画布网格像素为单位):

const { width, height } = canvas.getElementImageDefaultSize(myElement);
// Pre-allocate texture backing. Only do this once, or whenever the element
// changes size.
gl.texImage2D(
  /* target */ gl.TEXTURE_2D,
  /* level */ 0,
  /* internalformat */ gl.RGBA8,
  /* width */ Math.ceil(width),
  /* height */ Math.ceil(height),
  /* border */ 0,
  /* format */ gl.RGBA,
  /* type */ gl.UNSIGNED_BYTE,
  /* srcData */ null
);

将 DOM 快照上传为纹理

下一步是在渲染循环或 paint 监听器中将 HTML 元素上传到纹理中。

canvas.onpaint = () => {
  // 1. Pre-allocate texture backing first
  // ...

  // 2. Upload HTML snapshot into the WebGL texture
  gl.texElementSubImage2D(
    /* target  */ gl.TEXTURE_2D,
    /* level   */ 0,
    /* xoffset */ 0,
    /* yoffset */ 0,
    /* element */ myElement // or an ElementImage from `captureElementImage()`
  );

  // ...
};

同步几何图形

与 2D 上下文不同,浏览器无法在 3D 上下文中自动推断纹理在屏幕上的显示位置。您需要手动将 DOM 点击测试和无障碍边界框与 3D 网格对齐。

为此,请构建一个 DOMMatrix,将元素的边框框映射到其渲染的画布像素位置,然后使用 updateElementGeometry() 应用该矩阵:

canvas.addEventListener("paint", () => {
  // 1. Upload fresh DOM snapshot to GPU texture
  gl.bindTexture(gl.TEXTURE_2D, myTexture);
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, myElement);

  // 2. Draw your 3D scene
  draw3DScene();

  // 3. Construct the CSS-pixel transform mapping the element's
  // border box to its drawn position on the canvas as needed
  // (for example: scale and translate)
  const canvasTransform = new DOMMatrix();
  // scale X
  canvasTransform.a = canvas.clientWidth / myElement.offsetWidth;
  // scale Y
  canvasTransform.d = canvas.clientHeight / myElement.offsetHeight;
  // translate X (CSS px)
  canvasTransform.e = 50;
  // translate Y (CSS px)
  canvasTransform.f = 100;

  // 4. Update DOM position, accessibility focus rings, and pointer hit-testing
  canvas.updateElementGeometry(myElement, {
    canvasTransform: canvasTransform,
  });
});

后续步骤