Start · Sprachen · JavaScript · Referenz · WEBGL_draw_buffers extension

WEBGL_draw_buffers extension

Schnittstelle

WebGL-1-Erweiterung, mit der ein Fragment-Shader gleichzeitig in mehrere Farb-Anhänge (Multiple Render Targets, MRT) eines Framebuffers schreiben kann.

seit JavaScript WebGL 1 (Erweiterung); in WebGL 2 fester Bestandte Kategorie: webapi

Signatur

interface WEBGL_draw_buffers

Beschreibung

WEBGL_draw_buffers ist eine Erweiterung für WebGL 1. Man erhält sie über gl.getExtension("WEBGL_draw_buffers"). Sie ermöglicht Multiple Render Targets (MRT): Ein einziger Zeichendurchgang schreibt über gl_FragData[n] in mehrere Texturen, die als COLOR_ATTACHMENT0_WEBGL bis COLOR_ATTACHMENT15_WEBGL an einen Framebuffer gehängt sind.

Typischer Einsatz ist das Deferred Shading. Dabei landen Albedo, Normalen, Position oder Tiefe in einem Durchgang in einem G-Buffer. Auch Post-Processing-Pipelines und GPU-Picking nutzen die Erweiterung, wenn neben dem Farbbild ein ID-Puffer entstehen soll. So spart man mehrere Zeichendurchgänge mit identischer Geometrie ein.

Die Erweiterung stellt die Methode drawBuffersWEBGL(buffers) bereit. Dazu kommen Konstanten für die Farb-Anhänge (COLOR_ATTACHMENT0_WEBGL … COLOR_ATTACHMENT15_WEBGL), für die Draw-Buffer-Abfrage (DRAW_BUFFER0_WEBGL … DRAW_BUFFER15_WEBGL) sowie MAX_COLOR_ATTACHMENTS_WEBGL und MAX_DRAW_BUFFERS_WEBGL. Im GLSL-Shader aktiviert man die Erweiterung mit #extension GL_EXT_draw_buffers : require.

In WebGL 2 gibt es diese Erweiterung nicht mehr. Die Funktion ist dort fest eingebaut: gl.drawBuffers() mit gl.COLOR_ATTACHMENTn und in GLSL ES 3.00 mehrere out-Variablen mit layout(location = n).

Parameter

Name Typ Default Beschreibung
$buffers Pflicht Array<GLenum> Parameter von drawBuffersWEBGL(buffers): Liste der Ziele, in die geschrieben wird. Beim Framebuffer-Objekt muss Eintrag i entweder ext.COLOR_ATTACHMENTi_WEBGL oder gl.NONE sein. Beim Standard-Framebuffer (Canvas) ist nur [gl.BACK] oder [gl.NONE] erlaubt.

Beispiele

Erweiterung abfragen und Grenzen ermitteln

const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl");
const ext = gl.getExtension("WEBGL_draw_buffers");

if (!ext) {
  console.warn("MRT wird nicht unterstützt – Fallback auf mehrere Passes.");
} else {
  const maxAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
  const maxDrawBuffers = gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL);
  console.log(`Anhänge: ${maxAttachments}, Draw-Buffer: ${maxDrawBuffers}`);
}
Anhänge: 8, Draw-Buffer: 8

G-Buffer mit zwei Textur-Anhängen einrichten

const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("WEBGL_draw_buffers");
const { width, height } = gl.canvas;

const createTexture = () => {
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  return tex;
};

const albedoTex = createTexture();
const normalTex = createTexture();

const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, ext.COLOR_ATTACHMENT0_WEBGL, gl.TEXTURE_2D, albedoTex, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, ext.COLOR_ATTACHMENT1_WEBGL, gl.TEXTURE_2D, normalTex, 0);

// Beide Anhänge als Schreibziele aktivieren
ext.drawBuffersWEBGL([ext.COLOR_ATTACHMENT0_WEBGL, ext.COLOR_ATTACHMENT1_WEBGL]);

const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
console.log(status === gl.FRAMEBUFFER_COMPLETE ? "Framebuffer bereit" : `Fehler: ${status}`);
Framebuffer bereit

Fragment-Shader, der in zwei Ziele schreibt

const fragmentSource = `
#extension GL_EXT_draw_buffers : require
precision mediump float;
varying vec3 vNormal;
uniform vec4 uColor;

void main() {
  gl_FragData[0] = uColor;                                  // Albedo
  gl_FragData[1] = vec4(normalize(vNormal) * 0.5 + 0.5, 1.0); // Normale
}`;

const shader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(shader, fragmentSource);
gl.compileShader(shader);

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(shader));
}

Zum Vergleich: dasselbe in WebGL 2 ohne Erweiterung

const gl2 = document.querySelector("canvas").getContext("webgl2");
// Framebuffer mit COLOR_ATTACHMENT0/1 wie oben einrichten, dann:
gl2.drawBuffers([gl2.COLOR_ATTACHMENT0, gl2.COLOR_ATTACHMENT1]);

// GLSL ES 3.00:
// #version 300 es
// layout(location = 0) out vec4 outAlbedo;
// layout(location = 1) out vec4 outNormal;

// Wichtig · Fallstricke

  • Nur WebGL 1: In einem webgl2-Kontext gibt getExtension("WEBGL_draw_buffers") null zurück. Dort nutzt man gl.drawBuffers().
  • Verfügbarkeit: Die Unterstützung hängt von Hardware und Treiber ab. Auf Desktop ist sie weit verbreitet, auf älteren Mobilgeräten fehlt sie oft. Deshalb immer auf null prüfen und einen Fallback mit mehreren Passes bereithalten.
  • Reihenfolge: Eintrag i im Array von drawBuffersWEBGL() muss COLOR_ATTACHMENTi_WEBGL oder gl.NONE sein. Andernfalls entsteht INVALID_OPERATION. Die Array-Länge darf MAX_DRAW_BUFFERS_WEBGL nicht überschreiten.
  • Garantierte Mindestwerte: Laut Spezifikation sind mindestens 4 Anhänge und Draw-Buffer garantiert. Mehr ist nicht sicher.
  • Format: Alle Anhänge sollten dieselbe Größe haben, und in der Praxis auch dasselbe Format. Sonst meldet checkFramebufferStatus womöglich einen unvollständigen Framebuffer. Für Gleitkomma-Ziele braucht man zusätzlich OES_texture_float bzw. WEBGL_color_buffer_float.
  • Shader: Ohne #extension GL_EXT_draw_buffers : require kennt der Shader nur gl_FragData[0]. Wer gl_FragColor und gl_FragData im selben Shader mischt, bekommt einen Kompilierfehler.