Start · Sprachen · JavaScript · Referenz · ANGLE_instanced_arrays.drawElementsInstancedANGLE()

ANGLE_instanced_arrays.drawElementsInstancedANGLE()

Methode

Rendert indizierte Primitive aus einem Element-Array-Buffer mehrfach (instanziert) in einem einzigen Draw-Call – die WebGL-1-Erweiterung von <code>gl.drawElements()</code>.

seit JavaScript WebGL 1 Extension, Baseline: alle Browser (in WebG Kategorie: webapi

Signatur

ANGLE_instanced_arrays.drawElementsInstancedANGLE(mode, count, type, offset, primcount)

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');
}
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');
OK

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

// 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 mit vertexAttribDivisorANGLE(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_ENUM bei ungültigem mode/type; INVALID_VALUE bei negativem count oder primcount; INVALID_OPERATION, wenn kein ELEMENT_ARRAY_BUFFER gebunden ist oder offset nicht zur Typgröße passt.
  • gl.UNSIGNED_INT als Indextyp erfordert in WebGL 1 die Erweiterung OES_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_DRAW anlegen und per bufferSubData() aktualisieren.