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 メモリの再割り当てをトリガーすることなく、サブイメージの更新を高速に実行できます。

選択したサイズ パラメータと null に設定された srcData を使用して、次のようにバッファを事前に割り当てます。

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

次のステップ