Signatur
Beschreibung
drawElementsInstancedANGLE() gehört zur WebGL-1-Erweiterung ANGLE_instanced_arrays und arbeitet wie gl.drawElements(): Die Geometrie wird über Indizes aus dem gebundenen ELEMENT_ARRAY_BUFFER beschrieben. Zusätzlich wird dieselbe Geometrie primcount-mal gezeichnet – und zwar in einem einzigen Draw-Call.
Instanzierung lohnt sich immer dann, wenn viele gleichartige Objekte (Bäume, Partikel, Kacheln, Sprites) mit leicht unterschiedlichen Daten gerendert werden sollen. Pro-Instanz-Daten wie Position, Farbe oder Transformationsmatrix legt man in einem eigenen Vertex-Buffer ab und markiert das zugehörige Attribut mit ext.vertexAttribDivisorANGLE(location, 1). Dadurch rückt das Attribut nicht pro Vertex, sondern erst pro Instanz weiter.
Die Erweiterung muss zuerst über gl.getExtension('ANGLE_instanced_arrays') aktiviert werden. In WebGL 2 ist die Funktionalität fester Bestandteil der API und heißt dort gl.drawElementsInstanced() (ohne Suffix, ohne Erweiterung).
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $mode Pflicht | GLenum | Primitivtyp: gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN oder gl.TRIANGLES. |
|
| $count Pflicht | GLsizei | Anzahl der Indizes (Elemente), die pro Instanz gelesen werden. | |
| $type Pflicht | GLenum | Datentyp der Indizes: gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT oder – nur mit Erweiterung OES_element_index_uint – gl.UNSIGNED_INT. |
|
| $offset Pflicht | GLintptr | Byte-Offset im Element-Array-Buffer. Muss ein Vielfaches der Größe von type sein. |
|
| $primcount Pflicht | GLsizei | Anzahl der zu zeichnenden Instanzen. |
Beispiele
Erweiterung aktivieren und mit Fallback prüfen
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) {
console.warn('Instanzierung nicht verfügbar – Fallback auf Einzel-Draw-Calls');
} else {
console.log('ANGLE_instanced_arrays aktiv');
}
1000 Quads mit Pro-Instanz-Offset zeichnen
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
const vs = `
attribute vec2 aPos;
attribute vec2 aOffset;
void main() {
gl_Position = vec4(aPos * 0.02 + aOffset, 0.0, 1.0);
}`;
const fs = `
precision mediump float;
void main() { gl_FragColor = vec4(0.2, 0.7, 1.0, 1.0); }`;
const compile = (type, src) => {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
return s;
};
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vs));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(prog);
gl.useProgram(prog);
// Quad-Geometrie (4 Vertices, 6 Indizes)
const quad = new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const posBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(prog, 'aPos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
const idxBuf = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuf);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// Pro-Instanz-Offsets
const instances = 1000;
const offsets = new Float32Array(instances * 2);
for (let i = 0; i < offsets.length; i++) offsets[i] = Math.random() * 2 - 1;
const offBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, offBuf);
gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
const aOffset = gl.getAttribLocation(prog, 'aOffset');
gl.enableVertexAttribArray(aOffset);
gl.vertexAttribPointer(aOffset, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(aOffset, 1); // pro Instanz weiterschalten
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
ext.drawElementsInstancedANGLE(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0, instances);
console.log(gl.getError() === gl.NO_ERROR ? 'OK' : 'Fehler');
Gemeinsame Hilfsfunktion für WebGL 1 und WebGL 2
const createInstancedDrawer = (gl) => {
if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
return (mode, count, type, offset, n) =>
gl.drawElementsInstanced(mode, count, type, offset, n);
}
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) throw new Error('Instanzierung nicht unterstützt');
return (mode, count, type, offset, n) =>
ext.drawElementsInstancedANGLE(mode, count, type, offset, n);
};
const gl = document.createElement('canvas').getContext('webgl');
const drawInstanced = createInstancedDrawer(gl);
console.log(typeof drawInstanced);
// Wichtig · Fallstricke
- Divisor zurücksetzen: Der mit
vertexAttribDivisorANGLE()gesetzte Divisor ist globaler Zustand. Wird dieselbe Attribut-Location später für normale Draw-Calls verwendet, muss er mitvertexAttribDivisorANGLE(loc, 0)zurückgesetzt werden – sonst gibt es falsch gerenderte Geometrie. - Mindestens ein Attribut mit Divisor 0: Laut Spezifikation muss mindestens ein aktives Attribut einen Divisor von 0 haben, sonst erzeugt der Aufruf
INVALID_OPERATION. - Fehlerfälle:
INVALID_ENUMbei ungültigemmode/type;INVALID_VALUEbei negativemcountoderprimcount;INVALID_OPERATION, wenn keinELEMENT_ARRAY_BUFFERgebunden ist oderoffsetnicht zur Typgröße passt. gl.UNSIGNED_INTals Indextyp erfordert in WebGL 1 die ErweiterungOES_element_index_uint.- Kompatibilität: Die Erweiterung wird von praktisch allen WebGL-1-Implementierungen angeboten. In WebGL-2-Kontexten ist sie nicht verfügbar – dort
gl.drawElementsInstanced()verwenden. - Performance: Der Nutzen entsteht durch weniger Draw-Calls; die Anzahl der verarbeiteten Vertices bleibt gleich. Pro-Instanz-Buffer bei häufigen Updates mit
gl.DYNAMIC_DRAWanlegen und perbufferSubData()aktualisieren.