Signatur
Beschreibung
createTransformFeedback() erzeugt ein leeres WebGLTransformFeedback-Objekt. Es speichert, welche Puffer als Ziel für die Transform-Feedback-Ausgabe gebunden sind. Mit Transform Feedback schreibt die GPU die out-Variablen (Varyings) des Vertex-Shaders in einen oder mehrere Buffer, statt sie nur an den Rasterizer weiterzugeben. Die Daten bleiben dabei auf der Grafikkarte. Das eignet sich besonders für GPU-Partikelsysteme, Physiksimulationen oder mehrstufige Geometrieberechnungen.
Der typische Ablauf sieht so aus:
- Vor dem Linken des Programms mit
gl.transformFeedbackVaryings()festlegen, welche Varyings aufgezeichnet werden. - Das Objekt mit
createTransformFeedback()erzeugen und mitgl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf)binden. - Die Zielpuffer mit
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, index, buffer)anhängen. - Die Aufzeichnung mit
gl.beginTransformFeedback(primitiveMode)starten, zeichnen und mitgl.endTransformFeedback()beenden.
Das Objekt selbst enthält keine Daten, es merkt sich nur die gebundenen Puffer. Wenn du mehrere Objekte anlegst, z. B. eines pro Ping-Pong-Puffer, kannst du beim Zeichnen schnell zwischen den Zielkonfigurationen wechseln.
Rückgabewert
WebGLTransformFeedback-Objekt. Ist der Kontext verloren gegangen (context lost), wird null zurückgegeben.Beispiele
Transform-Feedback-Objekt erzeugen und binden
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) throw new Error('WebGL 2 wird nicht unterstützt');
const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
console.log(tf instanceof WebGLTransformFeedback);
console.log(gl.isTransformFeedback(tf));
Vertex-Shader-Ausgabe in einen Puffer schreiben und zurücklesen
const gl = document.createElement('canvas').getContext('webgl2');
const vsSource = `#version 300 es
in float inValue;
out float outValue;
void main() {
outValue = inValue * 2.0; // Verdoppeln auf der GPU
}`;
const fsSource = `#version 300 es
precision highp float;
out vec4 color;
void main() { color = vec4(0.0); }`;
const compile = (type, src) => {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
return s;
};
const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, vsSource));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fsSource));
// WICHTIG: vor linkProgram festlegen
gl.transformFeedbackVaryings(program, ['outValue'], gl.SEPARATE_ATTRIBS);
gl.linkProgram(program);
gl.useProgram(program);
const input = new Float32Array([1, 2, 3, 4]);
const inBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, inBuf);
gl.bufferData(gl.ARRAY_BUFFER, input, gl.STATIC_DRAW);
const loc = gl.getAttribLocation(program, 'inValue');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 1, gl.FLOAT, false, 0, 0);
const outBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, outBuf);
gl.bufferData(gl.ARRAY_BUFFER, input.byteLength, gl.STATIC_READ);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, outBuf);
gl.enable(gl.RASTERIZER_DISCARD); // keine Pixel zeichnen
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, input.length);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
const result = new Float32Array(input.length);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, outBuf);
gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, 0, result);
console.log(Array.from(result));
Ping-Pong für Partikel mit zwei Feedback-Objekten
const gl = document.createElement('canvas').getContext('webgl2');
const PARTICLES = 1000;
const makeBuffer = () => {
const b = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, b);
gl.bufferData(gl.ARRAY_BUFFER, PARTICLES * 4 * 4, gl.DYNAMIC_COPY);
return b;
};
// Zwei Zustände: aus A lesen, in B schreiben, danach tauschen
const states = [makeBuffer(), makeBuffer()].map((buffer) => {
const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffer);
return { buffer, tf };
});
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, null);
let current = 0;
const step = () => {
const src = states[current];
const dst = states[1 - current];
// ... src.buffer als Attribut binden, dann:
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, dst.tf);
// gl.beginTransformFeedback(gl.POINTS); gl.drawArrays(...); gl.endTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
current = 1 - current;
};
step();
console.log('Aktiver Zustand:', current);
// Aufräumen
states.forEach(({ buffer, tf }) => {
gl.deleteTransformFeedback(tf);
gl.deleteBuffer(buffer);
});