HTML ב-canvas של WebGL

עיבוד HTML ב-WebGL canvas מאפשר להשתמש בעצי משנה של DOM פעילים כמרקמי GPU דינמיים, להחיל אפקטים של הצללה על ה-DOM הפעיל ולשלב HTML אינטראקטיבי בסצנות תלת-ממדיות.

במדריך הזה נסביר איך לעבד HTML ל-canvas של WebGL. בסקירה הכללית על ה-API מוסבר איך להשתמש ב-HTML ב-Canvas.

כדי לעבד HTML ב-canvas של WebGL, צריך לבצע את השלבים הבאים:

  1. מכינים את הקנבס. מגדירים באופן הצהרתי את המאפיינים הנכונים כדי שהדפדפן ידע שהוא צריך לעבד HTML לתוך קנבס. ההגדרה הזו זהה בהקשרים של 2D,‏ WebGL ו-WebGPU. איך מגדירים את ה-canvas ואיך מגדירים רכיבי HTML שיאוירו באופן עצמאי.
  2. מחילים את ה-HTML כטקסטורה:
    1. הקצאה מראש של אחסון טקסטורות ב-GPU.
    2. מעלים את תמונת המצב של ה-DOM כמרקם.
  3. סנכרון ה-DOM והפיקסלים.

ציור רכיב HTML כטקסטורת WebGL

ב-WebGL, רכיבי HTML מועלים ל-GPU כטקסטורות דו-ממדיות באמצעות texElementSubImage2D().

הקצאה מראש של אחסון טקסטורות

בניגוד ל-canvas דו-ממדי, שמנהל את הזיכרון באופן אוטומטי, הקשר של 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 כטקסטורה

השלב הבא הוא להעלות את רכיב ה-HTML לטקסטורה בתוך לולאת הרינדור או מאזין 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()`
  );

  // ...
};

סנכרון הגיאומטריה

בניגוד להקשרים דו-ממדיים, הדפדפן לא יכול להסיק באופן אוטומטי איפה הטקסטורה מופיעה על המסך בהקשר תלת-ממדי. צריך להתאים באופן ידני את בדיקת מיקום המצביע ב-DOM ואת התיבות התוחמות של הנגישות לרשת התלת-ממדית.

כדי לעשות את זה, יוצרים 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,
  });
});

השלבים הבאים