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,
  });
});

後續步驟