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

ANGLE_instanced_arrays.drawArraysInstancedANGLE()

Methode

Rendert Primitive aus Array-Daten wie <code>gl.drawArrays()</code>, zeichnet dabei aber mehrere Instanzen der Geometrie mit einem einzigen Draw-Call.

seit JavaScript WebGL 1 (Erweiterung); in WebGL 2 als gl.drawArray Kategorie: webapi

Signatur

ANGLE_instanced_arrays.drawArraysInstancedANGLE(mode, first, count, primcount)

Beschreibung

drawArraysInstancedANGLE() gehört zur WebGL-1-Erweiterung ANGLE_instanced_arrays. Die Methode arbeitet wie gl.drawArrays(), zeichnet dieselbe Geometrie aber primcount-mal hintereinander. Nützlich ist das überall, wo viele gleichartige Objekte vorkommen: Partikel, Grasbüschel, Bäume, Kacheln oder Sprites.

Damit die Instanzen unterschiedlich aussehen, legt man pro Instanz Attribute an, etwa Position, Farbe oder Skalierung. Mit ext.vertexAttribDivisorANGLE(location, 1) legt man fest, dass ein Attribut nicht pro Vertex, sondern nur einmal pro Instanz weitergeschaltet wird. Attribute mit Divisor 0 (das ist die Voreinstellung) verhalten sich wie gewohnt pro Vertex.

Der große Vorteil ist die Leistung: Statt tausender einzelner drawArrays()-Aufrufe, die jeweils die CPU und den Treiber belasten, genügt ein Aufruf. Die Erweiterung holt man mit gl.getExtension('ANGLE_instanced_arrays'). In WebGL 2 ist die Funktion fest eingebaut und heißt dort gl.drawArraysInstanced().

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 aktivierten Attribut-Arrays.
$count Pflicht GLsizei Anzahl der Vertices, die pro Instanz gezeichnet werden.
$primcount Pflicht GLsizei Anzahl der Instanzen, die gezeichnet werden.

Rückgabewert

Typ
void
Beschreibung
Kein Rückgabewert. Fehler werden über gl.getError() gemeldet.

Beispiele

Erweiterung holen und prüfen

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');

if (!ext) {
  console.warn('Instancing wird nicht unterstützt');
} else {
  console.log(typeof ext.drawArraysInstancedANGLE);
}
function

100 Dreiecke mit Positionen pro Instanz 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.05 + aOffset, 0.0, 1.0);
  }`;
const fs = `
  precision mediump float;
  void main() { gl_FragColor = vec4(1.0, 0.5, 0.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);

// Geometrie: ein Dreieck (pro Vertex)
const posBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 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);

// Versatz pro Instanz (10 × 10 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);
ext.vertexAttribDivisorANGLE(aOffset, 1); // einmal pro Instanz weiterschalten

gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 3, 100);
Ein 10 × 10 Raster aus orangefarbenen Dreiecken, gezeichnet mit einem Draw-Call.

Einheitliche Hilfsfunktion für WebGL 1 und WebGL 2

const getInstancedDraw = (gl) => {
  if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
    return (mode, first, count, n) => gl.drawArraysInstanced(mode, first, count, n);
  }
  const ext = gl.getExtension('ANGLE_instanced_arrays');
  if (!ext) return null;
  return (mode, first, count, n) => ext.drawArraysInstancedANGLE(mode, first, count, n);
};

const gl = document.createElement('canvas').getContext('webgl');
const drawInstanced = getInstancedDraw(gl);
console.log(drawInstanced ? 'Instancing verfügbar' : 'Kein Instancing');
Instancing verfügbar

// Wichtig · Fallstricke

  • Divisor nicht vergessen: Ohne vertexAttribDivisorANGLE(loc, 1) werden Attribute pro Instanz wie Attribute pro Vertex gelesen. Dann kommt eine falsche Darstellung heraus oder es gibt einen Fehler wegen zu kurzer Puffer.
  • Mindestens ein Attribut mit Divisor 0: Wenn alle aktivierten Attribute einen Divisor ungleich 0 haben, meldet der Aufruf gl.INVALID_OPERATION.
  • Negative Werte für first, count oder primcount führen zu gl.INVALID_VALUE, ein ungültiger mode zu gl.INVALID_ENUM.
  • Die Puffer müssen groß genug sein: für Attribute pro Instanz mindestens ceil(primcount / divisor) Einträge. Sonst bricht WebGL den Aufruf ab.
  • Kompatibilität: Die Erweiterung wird praktisch überall unterstützt, wo WebGL 1 läuft. In einem WebGL-2-Kontext gibt es sie nicht, dort nimmt man gl.drawArraysInstanced().
  • Das Erweiterungsobjekt hängt am Kontext. Nach einem Kontextverlust (webglcontextlost) muss man es neu holen.