HTML na tela do WebGL

A renderização de HTML em uma tela WebGL permite usar subárvores DOM ativas como texturas de GPU dinâmicas, aplicar efeitos de sombreador ao DOM ativo e integrar HTML interativo em cenas 3D.

Este guia demonstra como renderizar HTML em uma tela do WebGL. Saiba mais sobre a HTML-in-Canvas na visão geral da API.

Para renderizar HTML em uma tela do WebGL, siga estas etapas:

  1. Prepare sua tela. Defina os atributos certos de forma declarativa para que o navegador saiba que deve renderizar o HTML em uma tela. Essa configuração é idêntica nos contextos 2D, WebGL e WebGPU. Saiba como configurar sua tela e como declarar elementos HTML para serem desenhados de forma independente.
  2. Aplique o HTML como uma textura:
    1. Pré-alocar o armazenamento de textura da GPU.
    2. Faça upload do snapshot do DOM como uma textura.
  3. Sincronize o DOM e os pixels.

Desenhar um elemento HTML como uma textura WebGL

No WebGL, os elementos HTML são enviados para a GPU como texturas 2D usando texElementSubImage2D().

Pré-alocar armazenamento de textura

Ao contrário de uma tela 2D, que gerencia a memória de backup automaticamente, um contexto WebGL requer gerenciamento de memória explícito.

Antes de fazer upload de pixels do DOM, é necessário pré-alocar a memória da GPU. Isso permite que o navegador faça atualizações rápidas de subimagens sem acionar realocações caras de memória da GPU.

Pré-aloque o buffer com os parâmetros de tamanho selecionados e srcData definido como null da seguinte maneira:

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

Para que a textura corresponda ao tamanho de layout natural do elemento HTML, use canvas.getElementImageDefaultSize() para recuperar o tamanho padrão em pixels da grade da tela:

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

Fazer upload do snapshot do DOM como uma textura

A próxima etapa é fazer upload do elemento HTML para a textura dentro do loop de renderização ou do listener paint.

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()`
  );

  // ...
};

Sincronizar geometria

Ao contrário dos contextos 2D, o navegador não consegue inferir automaticamente onde a textura aparece na tela em um contexto 3D. É necessário alinhar manualmente o teste de hit do DOM e as caixas delimitadoras de acessibilidade com a malha 3D.

Para fazer isso, construa um DOMMatrix que mapeie a caixa de borda do elemento para o local do pixel da tela renderizada e aplique-o usando 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,
  });
});

Próximas etapas