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

WebGL2RenderingContext.drawArraysInstanced()

Methode

Zeichnet dieselben Primitive aus Array-Daten mit einem einzigen Draw-Call mehrfach (<code>instanceCount</code>-mal), so wie ein wiederholter Aufruf von <code>gl.drawArrays()</code>.

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

Signatur

WebGL2RenderingContext.prototype.drawArraysInstanced(mode, first, count, instanceCount)

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

Typ
void
Beschreibung
Kein 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);
100 orangefarbene Dreiecke in einem 10×10-Raster

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);
64 Punkte auf einem Kreis

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

// 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, count oder instanceCount negativ, entsteht gl.INVALID_VALUE. Ein ungültiger mode erzeugt gl.INVALID_ENUM. Liest ein Attribut über das Ende seines Buffers hinaus, gibt es gl.INVALID_OPERATION. Das gilt auch für Instanz-Attribute, deren Buffer weniger als instanceCount / divisor Einträge hat.
  • gl_InstanceID gibt es nur in GLSL ES 3.00. Der Shader muss mit #version 300 es beginnen.
  • 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.