Signatur
Beschreibung
Mit framebufferTexture2D() verbindest du eine Textur mit einem Anhangspunkt des Framebuffers, der gerade an target gebunden ist. Danach landen alle Zeichenbefehle nicht mehr auf dem Canvas, sondern direkt in dieser Textur. Das ist die Grundlage für Render-to-Texture: Post-Processing (Blur, Bloom), Shadow Maps, Picking per Farb-ID, Spiegelungen oder mehrstufige Berechnungen auf der GPU.
Der übliche Ablauf ist: Textur mit texImage2D(..., null) in passender Größe anlegen, Framebuffer mit createFramebuffer() erzeugen und mit bindFramebuffer() binden, dann framebufferTexture2D() aufrufen und mit checkFramebufferStatus() prüfen, ob der Framebuffer vollständig ist. Für den Tiefenpuffer nimmt man meistens ein Renderbuffer über framebufferRenderbuffer(), außer man will die Tiefe später im Shader auslesen (z. B. bei Shadow Maps). Dann braucht man eine Tiefentextur.
Mit textarget legst du fest, welche Art von Textur angehängt wird: gl.TEXTURE_2D für normale Texturen oder eine der sechs Seiten gl.TEXTURE_CUBE_MAP_POSITIVE_X … NEGATIVE_Z, wenn du in eine Cubemap renderst, etwa für dynamische Umgebungskarten.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $target Pflicht | GLenum | — | Framebuffer-Ziel. In WebGL 1 nur gl.FRAMEBUFFER; in WebGL 2 zusätzlich gl.DRAW_FRAMEBUFFER und gl.READ_FRAMEBUFFER (gl.FRAMEBUFFER entspricht dort DRAW_FRAMEBUFFER). |
| $attachment Pflicht | GLenum | — | Anhangspunkt: gl.COLOR_ATTACHMENT0, gl.DEPTH_ATTACHMENT, gl.STENCIL_ATTACHMENT oder gl.DEPTH_STENCIL_ATTACHMENT. Mit WEBGL_draw_buffers bzw. in WebGL 2 auch COLOR_ATTACHMENT1 … COLOR_ATTACHMENT15. |
| $textarget Pflicht | GLenum | — | Texturziel: gl.TEXTURE_2D oder eine Cubemap-Seite wie gl.TEXTURE_CUBE_MAP_POSITIVE_X bis gl.TEXTURE_CUBE_MAP_NEGATIVE_Z. |
| $texture Pflicht | WebGLTexture | null | — | Die anzuhängende Textur. Mit null wird ein vorhandener Anhang wieder entfernt. |
| $level Pflicht | GLint | 0 | Mipmap-Stufe, in die gerendert wird. In WebGL 1 muss sie 0 sein; WebGL 2 erlaubt auch höhere Stufen. |
Rückgabewert
gl.getError() gemeldet (z. B. INVALID_ENUM, INVALID_OPERATION, wenn kein Framebuffer gebunden ist, oder INVALID_VALUE bei einem ungültigen level).Beispiele
Render-to-Texture: Farbtextur und Tiefen-Renderbuffer
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
const width = 512, height = 512;
// Zieltextur ohne Pixeldaten anlegen
const colorTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorTex);
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.LINEAR);
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);
// Tiefenpuffer als Renderbuffer
const depthRb = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthRb);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
// Framebuffer zusammenbauen
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRb);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
console.log(status === gl.FRAMEBUFFER_COMPLETE ? 'Framebuffer vollständig' : `Fehler: 0x${status.toString(16)}`);
// In die Textur rendern
gl.viewport(0, 0, width, height);
gl.clearColor(0.2, 0.4, 0.8, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Zurück zum Canvas, colorTex kann jetzt als Eingabe dienen
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
Tiefentextur für Shadow Mapping (WebGL 2)
const gl = document.querySelector('canvas').getContext('webgl2');
const size = 1024;
const depthTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, size, size, 0,
gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
const shadowFbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTex, 0);
// Keine Farbausgabe nötig
gl.drawBuffers([gl.NONE]);
gl.readBuffer(gl.NONE);
console.log(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
Alle sechs Seiten einer Cubemap rendern
const gl = document.querySelector('canvas').getContext('webgl');
const size = 256;
const cubeTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTex);
for (let i = 0; i < 6; i++) {
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, size, size, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
}
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.viewport(0, 0, size, size);
const colors = [[1,0,0],[0,1,0],[0,0,1],[1,1,0],[0,1,1],[1,0,1]];
colors.forEach(([r, g, b], i) => {
// Jeweils eine Seite anhängen und hineinzeichnen
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, cubeTex, 0);
gl.clearColor(r, g, b, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
});
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
console.log('Cubemap befüllt');