Signatur
Beschreibung
Mit setIndexBuffer() legt man fest, aus welchem GPUBuffer ein Render-Pass die Vertex-Indizes liest. Indizierte Geometrie spart Speicher und Bandbreite: Gemeinsam genutzte Eckpunkte (etwa die vier Ecken eines Rechtecks aus zwei Dreiecken) stehen nur einmal im Vertex-Buffer, und die Dreiecke verweisen per Index auf sie.
Die Einstellung gilt für alle folgenden drawIndexed()- und drawIndexedIndirect()-Aufrufe im selben Pass, bis ein neuer Index-Buffer gesetzt wird. Auf normale draw()-Aufrufe hat sie keinen Einfluss. Der Buffer muss mit dem Nutzungsflag GPUBufferUsage.INDEX angelegt worden sein.
Das indexFormat legt fest, wie breit ein Index ist: "uint16" (2 Byte, bis 65.535 Eckpunkte) oder "uint32" (4 Byte). Mit offset und size lässt sich nur ein Teilbereich des Buffers nutzen. So können mehrere Meshes in einem einzigen großen Buffer liegen.
Die Methode gibt es genauso bei GPURenderBundleEncoder. Sie zeichnet nur einen Befehl auf. Ausgeführt wird erst, wenn der Befehlspuffer über device.queue.submit() abgeschickt wird.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $buffer Pflicht | GPUBuffer | Buffer mit den Indexdaten. Muss mit GPUBufferUsage.INDEX erzeugt worden sein. |
|
| $indexFormat Pflicht | "uint16" | "uint32" | Datentyp der Indizes. Muss bei Strip-Topologien (line-strip, triangle-strip) zum stripIndexFormat der Pipeline passen. |
|
| $offset | number | 0 | Startposition im Buffer in Byte. Muss ein Vielfaches der Indexgröße sein (2 bei uint16, 4 bei uint32). |
| $size | number | buffer.size - offset | Länge des genutzten Bereichs in Byte. offset + size darf buffer.size nicht überschreiten. |
Rückgabewert
Beispiele
Rechteck aus zwei Dreiecken indiziert zeichnen
// Voraussetzung: device, context, pipeline (mit passendem Vertex-Layout) existieren
const vertices = new Float32Array([
-0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5
]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);
const indexBuffer = device.createBuffer({
size: indices.byteLength, // 12 Byte, Vielfaches von 4
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(indexBuffer, 0, indices);
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.setIndexBuffer(indexBuffer, 'uint16');
pass.drawIndexed(indices.length); // 6 Indizes -> 2 Dreiecke
pass.end();
device.queue.submit([encoder.finish()]);
Mehrere Meshes in einem gemeinsamen Index-Buffer
// Zwei Meshes liegen nacheinander in einem uint32-Index-Buffer
const meshA = new Uint32Array([0, 1, 2, 2, 3, 0]); // 24 Byte
const meshB = new Uint32Array([0, 1, 2]); // 12 Byte
const all = new Uint32Array([...meshA, ...meshB]);
const indexBuffer = device.createBuffer({
size: all.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(indexBuffer, 0, all);
// im Render-Pass:
pass.setIndexBuffer(indexBuffer, 'uint32', 0, meshA.byteLength);
pass.drawIndexed(meshA.length);
pass.setIndexBuffer(indexBuffer, 'uint32', meshA.byteLength, meshB.byteLength);
pass.drawIndexed(meshB.length);
// Alternative ohne erneutes setIndexBuffer: firstIndex von drawIndexed
// pass.drawIndexed(meshB.length, 1, meshA.length);
Indexformat abhängig von der Vertex-Anzahl wählen
const createIndexBuffer = (device, indexArray) => {
const format = indexArray instanceof Uint16Array ? 'uint16' : 'uint32';
// Größe auf 4 Byte aufrunden (writeBuffer verlangt Vielfache von 4)
const size = Math.ceil(indexArray.byteLength / 4) * 4;
const buffer = device.createBuffer({
size,
usage: GPUBufferUsage.INDEX,
mappedAtCreation: true
});
new indexArray.constructor(buffer.getMappedRange()).set(indexArray);
buffer.unmap();
return { buffer, format, count: indexArray.length };
};
const vertexCount = 70000;
const IndexArray = vertexCount > 65535 ? Uint32Array : Uint16Array;
const mesh = createIndexBuffer(device, new IndexArray([0, 1, 2]));
console.log(mesh.format);
pass.setIndexBuffer(mesh.buffer, mesh.format);
pass.drawIndexed(mesh.count);
// Wichtig · Fallstricke
- Nutzungsflag: Fehlt
GPUBufferUsage.INDEX, erzeugt der Aufruf einen Validierungsfehler. Das Werfen passiert nicht sofort, sondern der Encoder wird ungültig undencoder.finish()bzw.submit()scheitert. Fehler findet man überdevice.pushErrorScope('validation')oder das Eventuncapturederror. - Ausrichtung:
offsetmuss ein Vielfaches der Indexgröße sein.queue.writeBuffer()und die Buffergröße beimappedAtCreationverlangen Vielfache von 4 Byte. EinUint16Arraymit ungerader Länge muss man also auffüllen. - Formatwahl: Das Format muss zum Typed Array passen. Wer ein
Uint32Arrayals"uint16"ausliest, bekommt kaputte Geometrie, aber keine Fehlermeldung. - Primitive Restart: Bei Strip-Topologien beenden die Werte
0xFFFFbzw.0xFFFFFFFFden aktuellen Strip. Das Format muss dann mitprimitive.stripIndexFormatder Pipeline übereinstimmen. - Außerhalb des Bereichs:
drawIndexed()darf nicht mehr Indizes lesen, als im gesetzten Bereich liegen. Sonst gibt es einen Validierungsfehler. - Kompatibilität: WebGPU gibt es nur in sicheren Kontexten und noch nicht in allen Browsern. Prüfen Sie vorher
navigator.gpuund obrequestAdapter()einen Adapter liefert (Ergebnis ungleichnull).