Signatur
Beschreibung
beginTransformFeedback() startet den Transform-Feedback-Modus des aktuell gebundenen WebGLTransformFeedback-Objekts. Alle folgenden Zeichenaufrufe (drawArrays(), drawArraysInstanced()) schreiben die als „Varyings“ markierten Ausgaben des Vertex-Shaders in die Puffer, die an gl.TRANSFORM_FEEDBACK_BUFFER gebunden sind. Beendet wird der Modus mit endTransformFeedback().
Das ist nützlich für GPU-seitige Berechnungen, etwa Partikelsysteme, Physik-Simulationen oder vorab transformierte Geometrie. Die Daten bleiben dabei auf der GPU und müssen nicht in jedem Frame über JavaScript neu berechnet und hochgeladen werden. Ein typisches Muster ist das „Ping-Pong“: Zwei Puffer wechseln sich in jedem Frame als Eingabe und Ausgabe ab.
Vorher müssen die Ausgabe-Varyings mit transformFeedbackVaryings() festgelegt werden, und zwar vor linkProgram(). Außerdem muss das Programm mit useProgram() aktiv sein und für jede Varying ein Puffer per bindBufferBase() gebunden sein. Wenn nur berechnet und nichts gezeichnet werden soll, schaltet man mit gl.enable(gl.RASTERIZER_DISCARD) die Rasterisierung ab.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $primitiveMode Pflicht | GLenum | Primitivtyp, der während der Operation aufgezeichnet wird: gl.POINTS, gl.LINES oder gl.TRIANGLES. Er muss zum Modus der folgenden Draw-Aufrufe passen. Bei POINTS sind nur POINTS erlaubt, bei LINES auch LINE_STRIP und LINE_LOOP, bei TRIANGLES auch TRIANGLE_STRIP und TRIANGLE_FAN. |
Rückgabewert
undefined). Fehler erkennt man über gl.getError().Beispiele
Grundablauf: Vertex-Daten auf der GPU verdoppeln
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
const vsSource = `#version 300 es
in float inValue;
out float outValue;
void main() {
outValue = inValue * 2.0;
}`;
const fsSource = `#version 300 es
precision mediump 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));
// Varyings MÜSSEN vor linkProgram() festgelegt werden
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.TRANSFORM_FEEDBACK_BUFFER, outBuf);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, input.byteLength, gl.STATIC_READ);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_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); // nur berechnen, nicht 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);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, 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));
Partikel-Update per Ping-Pong-Puffer (Ausschnitt einer Render-Schleife)
// Annahme: updateProgram, vaos[2], buffers[2] und tf wurden vorher angelegt.
// Die Varyings wurden mit transformFeedbackVaryings(['vPosition', 'vVelocity'], gl.INTERLEAVED_ATTRIBS) festgelegt.
let read = 0;
const step = (deltaTime) => {
const write = 1 - read;
gl.useProgram(updateProgram);
gl.uniform1f(gl.getUniformLocation(updateProgram, 'uDelta'), deltaTime);
gl.bindVertexArray(vaos[read]); // Eingabe: alter Zustand
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffers[write]); // Ausgabe
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, PARTICLE_COUNT);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);
// Puffer lösen, bevor er als Vertex-Eingabe dient
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
read = write; // Rollen tauschen
};
Anzahl geschriebener Primitive per Query ermitteln
const query = gl.createQuery();
gl.beginQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
gl.beginTransformFeedback(gl.TRIANGLES);
gl.drawArrays(gl.TRIANGLES, 0, 30);
gl.endTransformFeedback();
gl.endQuery(gl.TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
// Ergebnisse sind asynchron verfügbar
const poll = () => {
if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
console.log('Primitive:', gl.getQueryParameter(query, gl.QUERY_RESULT));
} else {
requestAnimationFrame(poll);
}
};
poll();