Signatur
Beschreibung
drawElementsInstanced() kombiniert indiziertes Zeichnen über einen ELEMENT_ARRAY_BUFFER mit Instancing: Dieselbe Geometrie (z. B. ein Würfel, ein Baum, ein Partikel-Quad) wird instanceCount-mal gerendert. Pro Instanz können sich Attribute wie Position, Farbe oder Transformationsmatrix unterscheiden.
Welche Vertex-Attribute pro Instanz statt pro Vertex weitergeschaltet werden, legst du mit gl.vertexAttribDivisor(location, 1) fest. Im Vertex-Shader steht außerdem die eingebaute Variable gl_InstanceID (GLSL ES 3.00) zur Verfügung, mit der sich Instanzen auch ohne zusätzliche Attribute unterscheiden lassen.
Der große Vorteil: Statt tausender einzelner drawElements()-Aufrufe mit jeweils neuen Uniforms genügt ein einziger Aufruf. Das senkt den CPU-Overhead drastisch und ist die Standardtechnik für Partikelsysteme, Vegetation, Menschenmengen oder Tile-Maps.
In WebGL 1 ist die gleiche Funktionalität nur über die Erweiterung ANGLE_instanced_arrays als ext.drawElementsInstancedANGLE() verfügbar.
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 (number) | Anzahl der Indizes (nicht Dreiecke!), die pro Instanz aus dem Index-Buffer gelesen werden. | |
| $type Pflicht | GLenum | Datentyp der Indizes: gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT oder gl.UNSIGNED_INT (in WebGL 2 ohne Erweiterung nutzbar). |
|
| $offset Pflicht | GLintptr (number) | Byte-Offset im gebundenen Index-Buffer. Muss ein Vielfaches der Größe von type sein (z. B. 2 bei UNSIGNED_SHORT). |
|
| $instanceCount Pflicht | GLsizei (number) | Anzahl der zu zeichnenden Instanzen. Bei 0 wird nichts gezeichnet. |
Rückgabewert
gl.getError() gemeldet (z. B. INVALID_ENUM, INVALID_VALUE, INVALID_OPERATION).Beispiele
Minimales Setup: 100 Quads mit Instanz-Offsets
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
const vs = `#version 300 es
in vec2 aPos;
in vec2 aOffset; // pro Instanz
void main() {
gl_Position = vec4(aPos * 0.05 + aOffset, 0.0, 1.0);
}`;
const fs = `#version 300 es
precision mediump float;
out vec4 color;
void main() { color = 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);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Quad-Geometrie
const posBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, 1,1, -1,1]), gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(prog, 'aPos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
// Index-Buffer (2 Dreiecke)
const idxBuf = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuf);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2, 0,2,3]), gl.STATIC_DRAW);
// 100 Offsets im 10x10-Raster
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 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);
gl.vertexAttribDivisor(aOffset, 1); // einmal pro Instanz weiterschalten
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 6 Indizes pro Quad, 100 Instanzen
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, 100);
Instanzen über gl_InstanceID unterscheiden
// Vertex-Shader ohne Instanz-Attribut: Position wird aus gl_InstanceID berechnet
const vs = `#version 300 es
in vec2 aPos;
uniform float uTime;
void main() {
float a = float(gl_InstanceID) * 0.2 + uTime;
vec2 offset = vec2(cos(a), sin(a)) * 0.7;
gl_Position = vec4(aPos * 0.03 + offset, 0.0, 1.0);
}`;
// ... Programm, VAO und Index-Buffer wie im ersten Beispiel ...
const uTime = gl.getUniformLocation(prog, 'uTime');
const render = (t) => {
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1f(uTime, t / 1000);
gl.bindVertexArray(vao);
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, 32);
requestAnimationFrame(render);
};
requestAnimationFrame(render);
Fallback für WebGL 1 mit ANGLE_instanced_arrays
const getInstancedDraw = (gl) => {
if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
return (...args) => gl.drawElementsInstanced(...args);
}
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) throw new Error('Instancing wird nicht unterstützt');
return (...args) => ext.drawElementsInstancedANGLE(...args);
};
const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
const drawInstanced = getInstancedDraw(gl);
drawInstanced(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0, 500);
console.log(gl.getError() === gl.NO_ERROR);