Signatur
Beschreibung
Mit transformFeedbackVaryings() bestimmst du, welche out-Variablen des Vertex-Shaders beim Transform Feedback aufgezeichnet werden. Transform Feedback erlaubt es, die Ergebnisse des Vertex-Shaders direkt auf der GPU in einen WebGLBuffer zurückzuschreiben – ganz ohne Umweg über die CPU. Typische Anwendungen sind GPU-Partikelsysteme, physikalische Simulationen oder allgemeine Berechnungen (GPGPU) auf Vertex-Ebene.
Die Methode wirkt erst beim nächsten Aufruf von gl.linkProgram(program). Sie muss also vor dem Linken aufgerufen werden; eine nachträgliche Änderung erfordert ein erneutes Linken. Die Namen im Array müssen exakt den out-Variablen im Vertex-Shader entsprechen, sonst schlägt das Linken fehl.
Über bufferMode legst du fest, ob alle Variablen verschachtelt in einen einzigen Puffer geschrieben werden (gl.INTERLEAVED_ATTRIBS) oder jede Variable in einen eigenen Puffer an einem separaten Bindungsindex (gl.SEPARATE_ATTRIBS). Die Puffer werden anschließend mit gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, index, buffer) gebunden, und die Aufzeichnung läuft zwischen beginTransformFeedback() und endTransformFeedback().
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $program Pflicht | WebGLProgram | Das Shader-Programm, dessen Ausgaben aufgezeichnet werden sollen. Muss noch (erneut) gelinkt werden. | |
| $varyings Pflicht | string[] | Array mit den Namen der out-Variablen des Vertex-Shaders, die aufgezeichnet werden. Die Reihenfolge bestimmt die Anordnung im Puffer bzw. den Bindungsindex. |
|
| $bufferMode Pflicht | GLenum | gl.INTERLEAVED_ATTRIBS (alle Werte verschachtelt in einem Puffer) oder gl.SEPARATE_ATTRIBS (je Variable ein eigener Puffer). |
Rückgabewert
undefined). Fehler zeigen sich über gl.getError() bzw. den Link-Status des Programms.Beispiele
Programm für Transform Feedback vorbereiten
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
const vsSource = `#version 300 es
in float inValue;
out float outDoubled;
out float outSquared;
void main() {
outDoubled = inValue * 2.0;
outSquared = inValue * inValue;
}`;
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()
gl.transformFeedbackVaryings(program, ['outDoubled', 'outSquared'], gl.SEPARATE_ATTRIBS);
gl.linkProgram(program);
console.log(gl.getProgramParameter(program, gl.LINK_STATUS));
console.log(gl.getProgramParameter(program, gl.TRANSFORM_FEEDBACK_VARYINGS));
Ergebnisse berechnen und auslesen (Fortsetzung)
const input = new Float32Array([1, 2, 3, 4]);
// Eingabepuffer
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
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);
// Ausgabepuffer (SEPARATE_ATTRIBS: je einer pro Varying)
const makeOut = () => {
const b = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, b);
gl.bufferData(gl.ARRAY_BUFFER, input.byteLength, gl.STATIC_READ);
return b;
};
const outA = makeOut();
const outB = makeOut();
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, outA);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, outB);
gl.useProgram(program);
gl.enable(gl.RASTERIZER_DISCARD); // kein Rendering nötig
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 read = (buf) => {
const result = new Float32Array(input.length);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, buf);
gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, 0, result);
return result;
};
console.log(read(outA));
console.log(read(outB));
Verschachtelter Modus (INTERLEAVED_ATTRIBS)
// Beide Varyings landen abwechselnd in EINEM Puffer
gl.transformFeedbackVaryings(program, ['outDoubled', 'outSquared'], gl.INTERLEAVED_ATTRIBS);
gl.linkProgram(program); // erneut linken!
const info = gl.getTransformFeedbackVarying(program, 1);
console.log(info.name, info.size, info.type === gl.FLOAT);
// Pufferlayout: [d0, s0, d1, s1, ...]
// Wichtig · Fallstricke
- Reihenfolge: Der Aufruf wirkt erst beim nächsten
linkProgram(). Wer nach dem Linken aufruft, ohne neu zu linken, ändert nichts. - Unbekannte oder falsch geschriebene Namen führen zu einem Link-Fehler – prüfe
gl.getProgramInfoLog(program). Vom Compiler wegoptimierte (ungenutzte)out-Variablen gelten in der Regel dennoch als aktiv, solange sie im Array stehen. - Bei
gl.SEPARATE_ATTRIBSist die Anzahl begrenzt durchgl.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS(mindestens 4); beiINTERLEAVED_ATTRIBSdurchMAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS(mindestens 64 Komponenten). - Ein Puffer darf nicht gleichzeitig als Vertex-Eingabe und als Transform-Feedback-Ziel gebunden sein – für Simulationen daher zwei Puffer abwechselnd verwenden („Ping-Pong“).
- Ist das Programm während aktiver Transform-Feedback-Aufzeichnung in Benutzung, erzeugt ein erneutes Linken
INVALID_OPERATION. - Nur in WebGL 2 verfügbar (alle modernen Browser); in WebGL 1 existiert Transform Feedback nicht.
getBufferSubData()ist synchron und kann die Pipeline blockieren – in Produktivcode ggf. mitfenceSync()arbeiten.