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

WebGL2RenderingContext.drawElementsInstanced()

Methode

Rendert indizierte Primitive wie <code>gl.drawElements()</code>, aber gleich mehrfach (instanziert) in einem einzigen Draw-Call.

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

Signatur

WebGL2RenderingContext.prototype.drawElementsInstanced(mode, count, type, offset, instanceCount)

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

Typ
void
Beschreibung
Kein Rückgabewert. Fehler werden über 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);
100 blaue Quadrate im 10×10-Raster – mit nur einem Draw-Call.

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);
32 Quads kreisen animiert um die Bildmitte.

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);
true