Signatur
Beschreibung
Die Methode multiDrawArraysInstancedWEBGL() gehört zur Erweiterung WEBGL_multi_draw. Sie fasst mehrere Aufrufe von drawArraysInstanced() zu einem einzigen API-Aufruf zusammen. Das Ergebnis entspricht einer Schleife über drawcount Einträge, in der jeweils gl.drawArraysInstanced(mode, firsts[i], counts[i], instanceCounts[i]) aufgerufen wird. Der Aufwand in JavaScript und im Treiber fällt dabei aber nur einmal an.
Das lohnt sich, wenn viele kleine Objekte mit derselben Shader- und Zustandskonfiguration gezeichnet werden. Beispiele sind Partikelsysteme, Vegetation, Kacheln oder ein Text-Renderer, bei denen die Geometrien hintereinander im selben Vertex-Puffer liegen. Die Zahl der Zeichenaufrufe (engl. „draw calls“) sinkt so deutlich, was vor allem bei einer CPU-Engstelle spürbar Leistung bringt.
Im Vertex-Shader kann man mit der eingebauten Variable gl_DrawID feststellen, welcher Teil-Zeichenbefehl gerade läuft. Dafür muss der Shader #extension GL_ANGLE_multi_draw : require deklarieren. Mit gl_DrawID lässt sich pro Teil-Befehl zum Beispiel eine Farbe oder eine Transformation aus einem Uniform-Array oder einer Textur auswählen.
Die Listen für Startindizes, Vertex-Anzahlen und Instanzanzahlen können Int32Arrays oder normale Arrays sein. Mit den zugehörigen Offsets ab dem jeweiligen Listenanfang lassen sich Teilbereiche größerer Puffer verwenden, ohne sie zu kopieren.
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. |
|
| $firstsList Pflicht | Int32Array | Array<number> | Liste der Start-Vertex-Indizes, einer pro Teil-Zeichenbefehl. | |
| $firstsOffset Pflicht | number | Position in firstsList, ab der gelesen wird. |
|
| $countsList Pflicht | Int32Array | Array<number> | Liste mit der Anzahl zu zeichnender Vertices pro Teil-Zeichenbefehl. | |
| $countsOffset Pflicht | number | Position in countsList, ab der gelesen wird. |
|
| $instanceCountsList Pflicht | Int32Array | Array<number> | Liste mit der Anzahl der Instanzen pro Teil-Zeichenbefehl. | |
| $instanceCountsOffset Pflicht | number | Position in instanceCountsList, ab der gelesen wird. |
|
| $drawcount Pflicht | number | Anzahl der auszuführenden Teil-Zeichenbefehle. Jede Liste muss ab ihrem Offset mindestens so viele Einträge haben. |
Rückgabewert
gl.getError() gemeldet, zum Beispiel gl.INVALID_VALUE bei negativen Werten oder zu kurzen Listen.Beispiele
Erweiterung abfragen und drei Geometrien instanziert zeichnen
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
const ext = gl.getExtension('WEBGL_multi_draw');
if (!ext) {
console.warn('WEBGL_multi_draw wird nicht unterstützt');
} else {
// Voraussetzung: Programm, VAO und Vertex-Puffer sind gebunden.
// Im Puffer liegen hintereinander: Dreieck (3), Quad (6), Hexagon (18)
const firsts = new Int32Array([0, 3, 9]);
const counts = new Int32Array([3, 6, 18]);
const instanceCounts = new Int32Array([100, 50, 10]);
ext.multiDrawArraysInstancedWEBGL(
gl.TRIANGLES,
firsts, 0,
counts, 0,
instanceCounts, 0,
firsts.length
);
console.log(gl.getError() === gl.NO_ERROR);
}
gl_DrawID im Vertex-Shader verwenden
const vsSource = `#version 300 es
#extension GL_ANGLE_multi_draw : require
in vec2 aPos;
in vec2 aOffset; // pro Instanz (vertexAttribDivisor = 1)
uniform vec3 uColors[3];
out vec3 vColor;
void main() {
vColor = uColors[gl_DrawID]; // Farbe je Teil-Zeichenbefehl
gl_Position = vec4(aPos * 0.05 + aOffset, 0.0, 1.0);
}`;
const fsSource = `#version 300 es
precision mediump float;
in vec3 vColor;
out vec4 outColor;
void main() { outColor = vec4(vColor, 1.0); }`;
// Die Erweiterung vor dem Kompilieren aktivieren:
const ext = gl.getExtension('WEBGL_multi_draw');
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 vs = compile(gl.VERTEX_SHADER, vsSource);
const fs = compile(gl.FRAGMENT_SHADER, fsSource);
Teilbereich eines großen Befehlspuffers per Offset zeichnen
// Befehle für 1000 Objekte, pro Frame wird nur ein sichtbarer Ausschnitt gezeichnet
const MAX = 1000;
const firsts = new Int32Array(MAX);
const counts = new Int32Array(MAX).fill(6);
const instances = new Int32Array(MAX).fill(1);
for (let i = 0; i < MAX; i++) firsts[i] = i * 6;
const drawRange = (start, n) => {
ext.multiDrawArraysInstancedWEBGL(
gl.TRIANGLES,
firsts, start,
counts, start,
instances, start,
n
);
};
drawRange(200, 150); // zeichnet die Objekte 200 bis 349