Start · Sprachen · JavaScript · Referenz · WebGL2RenderingContext.vertexAttribDivisor()

WebGL2RenderingContext.vertexAttribDivisor()

Methode

Legt fest, wie oft ein Vertex-Attribut beim instanzierten Zeichnen weiterrückt: pro Vertex (<code>0</code>) oder nur alle <code>n</code> Instanzen.

seit JavaScript WebGL 2 – Baseline: alle modernen Browser (seit 20 Kategorie: webapi

Signatur

WebGL2RenderingContext.prototype.vertexAttribDivisor(index, divisor)

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

Typ
void
Beschreibung
Kein 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);
10 true

// Wichtig · Fallstricke

  • Nur WebGL 2: In einem webgl-Kontext (WebGL 1) gibt es die Methode nicht. Dort gl.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 0 setzen.
  • Matrizen: mat3/mat4 belegen 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 Zeichnen INVALID_OPERATION.
  • Im Shader lässt sich alternativ gl_InstanceID nutzen, zum Beispiel für Daten, die aus Texturen oder UBOs gelesen werden.