Signatur
Beschreibung
vertexAttribDivisor() ist die Grundlage für Instanced Rendering in WebGL 2. Normalerweise liest die GPU für jeden Vertex den nächsten Wert eines Attribut-Puffers. Mit einem Divisor größer als 0 gilt stattdessen: Das Attribut rückt erst weiter, wenn divisor Instanzen gezeichnet wurden. So lassen sich pro Instanz eigene Daten wie Position, Farbe oder Transformationsmatrix übergeben, während die Geometrie (z. B. ein Würfel-Mesh) für alle Instanzen gleich bleibt.
Typischerweise setzt man für Geometrie-Attribute (Position, Normale, UV) den Divisor 0 und für Instanz-Attribute (Offset, Farbe, Modellmatrix) den Divisor 1. Gezeichnet wird anschließend mit drawArraysInstanced() oder drawElementsInstanced(). So entstehen tausende Objekte mit einem einzigen Draw-Call, was deutlich schneller ist als viele einzelne Aufrufe.
Der Divisor gehört zum Zustand des aktuell gebundenen WebGLVertexArrayObject (VAO). Wer VAOs nutzt, muss den Divisor also nur einmal beim Aufbau des VAO setzen. Ein mat4-Attribut belegt vier aufeinanderfolgende Attribut-Positionen, und für jede davon muss der Divisor einzeln gesetzt werden.
In WebGL 1 steht dieselbe Funktion über die Erweiterung ANGLE_instanced_arrays als vertexAttribDivisorANGLE() zur Verfügung.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $index Pflicht | GLuint (number) | Position des Vertex-Attributs, z. B. aus gl.getAttribLocation() oder per layout(location = n) im Shader festgelegt. Muss kleiner als gl.MAX_VERTEX_ATTRIBS sein. |
|
| $divisor Pflicht | GLuint (number) | 0 | Anzahl der Instanzen, nach denen das Attribut weiterrückt. 0 = pro Vertex (Standard), 1 = pro Instanz, n = alle n Instanzen. |
Rückgabewert
undefined). Bei ungültigem index wird der Fehler gl.INVALID_VALUE gesetzt, abrufbar über gl.getError().Beispiele
1000 Quadrate mit individuellem Offset in einem Draw-Call
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
const vs = `#version 300 es
layout(location = 0) in vec2 aPosition; // pro Vertex
layout(location = 1) in vec2 aOffset; // pro Instanz
void main() {
gl_Position = vec4(aPosition * 0.02 + aOffset, 0.0, 1.0);
}`;
const fs = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() { outColor = vec4(1.0, 0.5, 0.2, 1.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, vs));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(program);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Geometrie: ein Quadrat aus zwei Dreiecken
const quad = new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(0, 0); // pro Vertex (Standard)
// Instanzdaten: zufällige Offsets
const count = 1000;
const offsets = new Float32Array(count * 2).map(() => Math.random() * 2 - 1);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(1, 1); // pro Instanz weiterrücken
gl.useProgram(program);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, count);
mat4 als Instanz-Attribut: Divisor für alle vier Spalten setzen
// Shader: layout(location = 2) in mat4 aModel;
const loc = 2;
const instances = 500;
const matrices = new Float32Array(instances * 16);
// ... Matrizen befüllen ...
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, matrices, gl.DYNAMIC_DRAW);
const bytesPerMatrix = 16 * 4;
for (let i = 0; i < 4; i++) {
const column = loc + i;
gl.enableVertexAttribArray(column);
gl.vertexAttribPointer(column, 4, gl.FLOAT, false, bytesPerMatrix, i * 16);
gl.vertexAttribDivisor(column, 1); // jede Spalte einzeln!
}
Divisor > 1 und Abfrage des aktuellen Werts
// Eine Farbe gilt jeweils für 10 aufeinanderfolgende Instanzen
gl.vertexAttribDivisor(3, 10);
const divisor = gl.getVertexAttrib(3, gl.VERTEX_ATTRIB_ARRAY_DIVISOR);
console.log(divisor);
console.log(gl.getError() === gl.NO_ERROR);
// Wichtig · Fallstricke
- Nur WebGL 2: In einem
webgl-Kontext (WebGL 1) gibt es die Methode nicht. Dortgl.getExtension('ANGLE_instanced_arrays').vertexAttribDivisorANGLE()verwenden. - VAO-Zustand: Der Divisor wird im aktuell gebundenen VAO gespeichert. Wer ein anderes VAO bindet, ohne dort den Divisor zu setzen, bekommt den Standardwert
0. Das ist ein häufiger Grund, warum „alle Instanzen an derselben Stelle“ landen. - Divisor zurücksetzen: Wer ohne VAOs arbeitet und dieselbe Attribut-Position später für normales Zeichnen wiederverwendet, muss den Divisor wieder auf
0setzen. - Matrizen:
mat3/mat4belegen 3 bzw. 4 Positionen, und der Divisor muss für jede davon einzeln gesetzt werden. - Puffergröße: Der Instanzpuffer muss mindestens
ceil(instanceCount / divisor)Einträge enthalten, sonst meldet WebGL beim ZeichnenINVALID_OPERATION. - Im Shader lässt sich alternativ
gl_InstanceIDnutzen, zum Beispiel für Daten, die aus Texturen oder UBOs gelesen werden.