Start · Sprachen · JavaScript · Referenz · WebGLRenderingContext.readPixels()

WebGLRenderingContext.readPixels()

Methode

Liest einen rechteckigen Pixelblock aus dem aktuell gebundenen Farb-Framebuffer in ein <code>ArrayBufferView</code> wie <code>Uint8Array</code>.

seit JavaScript WebGL 1.0 – Baseline: alle Browser Kategorie: webapi

Signatur

WebGLRenderingContext.prototype.readPixels(x, y, width, height, format, type, pixels)

Beschreibung

readPixels() kopiert Pixeldaten von der GPU in den Arbeitsspeicher des Browsers. Gelesen wird aus dem aktuell gebundenen Framebuffer: entweder dem Standard-Framebuffer (der Canvas) oder einem mit bindFramebuffer() gebundenen WebGLFramebuffer. Das Ergebnis landet in dem übergebenen TypedArray, das groß genug sein muss.

Typische Anwendungsfälle sind Color-Picking (Objekt-IDs werden als Farben in einen Offscreen-Buffer gerendert und unter dem Mauszeiger ausgelesen), Screenshots, GPU-Berechnungen (GPGPU) und automatisierte Tests, die Render-Ergebnisse vergleichen.

Wichtig: Der Koordinatenursprung (0, 0) liegt in WebGL unten links, nicht oben links wie im DOM. Mauskoordinaten müssen daher vertikal umgerechnet werden. Außerdem ist die Zeilenreihenfolge der ausgelesenen Daten von unten nach oben.

In WebGL 1 ist die Kombination gl.RGBA + gl.UNSIGNED_BYTE immer erlaubt. Eine zweite, implementierungsabhängige Kombination lässt sich über gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT) und gl.IMPLEMENTATION_COLOR_READ_TYPE abfragen.

Parameter

Name Typ Default Beschreibung
$x Pflicht number — Horizontale Koordinate der linken unteren Ecke des Rechtecks (in Pixeln, GLint).
$y Pflicht number — Vertikale Koordinate der linken unteren Ecke – gemessen von unten.
$width Pflicht number — Breite des Rechtecks in Pixeln.
$height Pflicht number — Höhe des Rechtecks in Pixeln.
$format Pflicht GLenum — Format der Pixeldaten, z. B. gl.RGBA, gl.RGB oder gl.ALPHA. Garantiert unterstützt ist nur gl.RGBA.
$type Pflicht GLenum — Datentyp pro Komponente: gl.UNSIGNED_BYTE (→ Uint8Array), gl.UNSIGNED_SHORT_5_6_5, gl.UNSIGNED_SHORT_4_4_4_4, gl.UNSIGNED_SHORT_5_5_5_1 (→ Uint16Array) oder gl.FLOAT (→ Float32Array, nur mit Erweiterung).
$pixels Pflicht ArrayBufferView | null — Zielpuffer, dessen Typ zu type passen muss. Größe mindestens width * height * Komponenten (unter Beachtung von PACK_ALIGNMENT). Bei null wird ein INVALID_VALUE-Fehler erzeugt.

Rückgabewert

Typ
void
Beschreibung
Kein Rückgabewert (undefined). Die Daten werden direkt in pixels geschrieben; Fehler werden über gl.getError() gemeldet.

Beispiele

Farbe eines einzelnen Pixels lesen

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');

// Canvas rot einfärben
gl.clearColor(1, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
console.log(pixel);
Uint8Array(4) [255, 0, 0, 255]

Color-Picking: Pixel unter dem Mauszeiger auslesen

canvas.addEventListener('click', (event) => {
  const rect = canvas.getBoundingClientRect();
  // CSS-Pixel in Framebuffer-Pixel umrechnen
  const x = Math.floor((event.clientX - rect.left) * canvas.width / rect.width);
  const yTop = Math.floor((event.clientY - rect.top) * canvas.height / rect.height);
  const y = canvas.height - yTop - 1; // WebGL-Ursprung liegt unten links

  gl.bindFramebuffer(gl.FRAMEBUFFER, pickingFramebuffer);
  renderPickingScene(); // zeichnet Objekt-IDs als Farben

  const data = new Uint8Array(4);
  gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, data);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);

  const id = data[0] | (data[1] << 8) | (data[2] << 16);
  console.log('Getroffenes Objekt:', id);
});
Getroffenes Objekt: 42

Kompletten Framebuffer als Bild exportieren (Zeilen spiegeln)

const exportCanvas = (gl) => {
  const { drawingBufferWidth: w, drawingBufferHeight: h } = gl;
  const pixels = new Uint8Array(w * h * 4);
  gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

  // WebGL liefert Zeilen von unten nach oben – für ImageData umdrehen
  const flipped = new Uint8ClampedArray(w * h * 4);
  const rowBytes = w * 4;
  for (let row = 0; row < h; row++) {
    const src = (h - row - 1) * rowBytes;
    flipped.set(pixels.subarray(src, src + rowBytes), row * rowBytes);
  }

  const out = document.createElement('canvas');
  out.width = w;
  out.height = h;
  out.getContext('2d').putImageData(new ImageData(flipped, w, h), 0, 0);
  return out.toDataURL('image/png');
};

console.log(exportCanvas(gl).slice(0, 22));
data:image/png;base64,

// Wichtig · Fallstricke

  • Leerer Canvas: Ohne preserveDrawingBuffer: true beim getContext() wird der Zeichenpuffer nach dem Compositing gelöscht. readPixels() muss daher im selben Frame (z. B. im selben requestAnimationFrame-Callback) wie das Rendern aufgerufen werden, sonst erhält man nur Nullen.
  • Performance: Der Aufruf ist synchron und erzwingt, dass die GPU alle ausstehenden Befehle abarbeitet (Pipeline-Stall). Nicht in jedem Frame für große Bereiche verwenden. In WebGL 2 lässt sich mit einem PIXEL_PACK_BUFFER und fenceSync() asynchron lesen.
  • Typ-Konsistenz: Der Array-Typ muss zu type passen (UNSIGNED_BYTE → Uint8Array), sonst entsteht INVALID_OPERATION. Ein Uint8ClampedArray ist in WebGL 1 nicht erlaubt.
  • Ausrichtung: gl.PACK_ALIGNMENT (Standard 4) beeinflusst die Zeilenlänge im Puffer – bei gl.RGB und ungeraden Breiten ggf. gl.pixelStorei(gl.PACK_ALIGNMENT, 1) setzen.
  • Außerhalb liegende Pixel (außerhalb des Framebuffers) werden nicht beschrieben bzw. bleiben 0.
  • Sicherheit: Wurden Cross-Origin-Bilder ohne CORS-Freigabe als Textur verwendet, ist der Canvas „tainted“; WebGL verbietet solche Texturen bereits bei texImage2D() mit einem SecurityError. Mit antialias und High-DPI-Displays auf drawingBufferWidth/drawingBufferHeight statt CSS-Größen achten.
  • WebGL 2 (WebGL2RenderingContext) bietet zusätzliche Überladungen mit Offset bzw. Pufferziel.