Signatur
Beschreibung
drawArraysInstanced() ist die Instanced-Variante von gl.drawArrays(). Die Methode liest Vertex-Daten fortlaufend aus den gebundenen Array-Buffern und zeichnet dieselbe Geometrie instanceCount-mal. Das erledigt ein einziger Draw-Call. So lassen sich Tausende gleichartige Objekte wie Partikel, Bäume, Gras oder Sprites mit wenig CPU-Aufwand darstellen.
Die Instanzen unterscheiden sich über Instanz-Attribute. Das sind Vertex-Attribute, für die mit gl.vertexAttribDivisor(index, 1) ein Divisor gesetzt ist. Sie wechseln ihren Wert nicht pro Vertex, sondern nur pro Instanz (bei Divisor n alle n Instanzen). Im Vertex-Shader (GLSL ES 3.00) steht außerdem die eingebaute Variable gl_InstanceID zur Verfügung. Sie enthält den Index der aktuellen Instanz, beginnend bei 0.
In WebGL 1 gab es diese Funktion nur über die Erweiterung ANGLE_instanced_arrays (ext.drawArraysInstancedANGLE()). In WebGL 2 gehört sie zum Kern. Wenn die Geometrie über einen Index-Buffer beschrieben ist, nimmst du stattdessen drawElementsInstanced().
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $mode Pflicht | GLenum | Art der Primitive: gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN oder gl.TRIANGLES. |
|
| $first Pflicht | GLint | Index des ersten Vertex in den Vertex-Arrays. | |
| $count Pflicht | GLsizei | Anzahl der Vertices, die pro Instanz gezeichnet werden. | |
| $instanceCount Pflicht | GLsizei | Anzahl der Instanzen, die gezeichnet werden. Bei 0 wird nichts gezeichnet. |
Rückgabewert
undefined). Fehler meldet die Methode über gl.getError().Beispiele
Viele Dreiecke mit Offset pro Instanz
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
const vsSource = `#version 300 es
in vec2 aPosition;
in vec2 aOffset; // pro Instanz
void main() {
gl_Position = vec4(aPosition * 0.1 + aOffset, 0.0, 1.0);
}`;
const fsSource = `#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);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
return s;
};
const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, vsSource));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fsSource));
gl.linkProgram(program);
gl.useProgram(program);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Geometrie: ein Dreieck (pro Vertex)
const posLoc = gl.getAttribLocation(program, 'aPosition');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 1, -1, -1, 1, -1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
// Offsets: 100 Instanzen in einem 10x10-Raster (pro Instanz)
const offsets = new Float32Array(200);
for (let i = 0; i < 100; i++) {
offsets[i * 2] = (i % 10) / 5 - 0.9;
offsets[i * 2 + 1] = Math.floor(i / 10) / 5 - 0.9;
}
const offLoc = gl.getAttribLocation(program, 'aOffset');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
gl.enableVertexAttribArray(offLoc);
gl.vertexAttribPointer(offLoc, 2, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(offLoc, 1); // pro Instanz weiterschalten
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 3 Vertices pro Instanz, 100 Instanzen, ein Draw-Call
gl.drawArraysInstanced(gl.TRIANGLES, 0, 3, 100);
Nur gl_InstanceID nutzen, ohne Instanz-Buffer
const gl = document.querySelector('canvas').getContext('webgl2');
const vs = `#version 300 es
void main() {
// Punkte auf einem Kreis, nur über gl_InstanceID berechnet
float a = float(gl_InstanceID) / 64.0 * 6.2831853;
gl_Position = vec4(cos(a) * 0.8, sin(a) * 0.8, 0.0, 1.0);
gl_PointSize = 6.0;
}`;
const fs = `#version 300 es
precision mediump float;
out vec4 c;
void main() { c = vec4(0.2, 0.8, 1.0, 1.0); }`;
const prog = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
gl.attachShader(prog, sh);
}
gl.linkProgram(prog);
gl.useProgram(prog);
gl.clear(gl.COLOR_BUFFER_BIT);
// 1 Vertex pro Instanz, 64 Instanzen
gl.drawArraysInstanced(gl.POINTS, 0, 1, 64);
Fallback für WebGL 1 mit ANGLE_instanced_arrays
const canvas = document.querySelector('canvas');
const gl2 = canvas.getContext('webgl2');
const drawInstanced = gl2
? (mode, first, count, n) => gl2.drawArraysInstanced(mode, first, count, n)
: (() => {
const gl1 = canvas.getContext('webgl');
const ext = gl1?.getExtension('ANGLE_instanced_arrays');
if (!ext) throw new Error('Instancing wird nicht unterstützt');
return (mode, first, count, n) => ext.drawArraysInstancedANGLE(mode, first, count, n);
})();
console.log(typeof drawInstanced);
// Wichtig · Fallstricke
- Ohne
gl.vertexAttribDivisor(loc, 1)behandelt WebGL ein Attribut als normales Vertex-Attribut. Alle Instanzen landen dann an derselben Stelle. Das ist ein sehr häufiger Fehler. - Der Divisor gehört zum Zustand des gebundenen Vertex Array Objects (VAO). Setze ihn deshalb, während das richtige VAO gebunden ist.
- Ist
first,countoderinstanceCountnegativ, entstehtgl.INVALID_VALUE. Ein ungültigermodeerzeugtgl.INVALID_ENUM. Liest ein Attribut über das Ende seines Buffers hinaus, gibt esgl.INVALID_OPERATION. Das gilt auch für Instanz-Attribute, deren Buffer weniger alsinstanceCount / divisorEinträge hat. gl_InstanceIDgibt es nur in GLSL ES 3.00. Der Shader muss mit#version 300 esbeginnen.- Instancing bringt vor allem dann etwas, wenn viele kleine, gleichartige Objekte gezeichnet werden. Bei wenigen großen Meshes ist der Gewinn gering.
- Kompatibilität: WebGL 2 läuft in allen aktuellen Browsern, Safari ab Version 15. Für ältere Umgebungen gibt es die WebGL-1-Erweiterung
ANGLE_instanced_arrays.