Problem: wywołania rysowania
Kiedy tworzysz 10 000 obiektów Mesh w Three.js i
dodajesz je do sceny, każdy z nich generuje przy renderowaniu
osobne wywołanie rysowania (draw call). Wywołanie
rysowania to polecenie CPU wysyłane do GPU, które mówi: podepnij
ten bufor, zastosuj tę macierz transformacji, narysuj tę geometrię.
GPU potrafi przetwarzać miliony poligonów na sekundę — ale jest w stanie obsłużyć jedynie kilka tysięcy zmian stanu wywołań rysowania na sekundę. Wąskie gardło CPU zabija liczbę klatek na długo zanim zabraknie ci złożoności geometrycznej.
| Liczba obiektów | Metoda | Wywołania rysowania | FPS |
|---|---|---|---|
| 1 000 | Mesh ×1000 | 1 000 | 55 |
| 10 000 | Mesh ×10000 | 10 000 | 12 |
| 100 000 | Mesh ×100000 | 100 000 | 1 |
| 100 000 | InstancedMesh | 1 | 60 |
Rozwiązanie: InstancedMesh
InstancedMesh pakuje wszystkie transformacje instancji
do jednego bufora GPU i renderuje wszystko w jednym wywołaniu
rysowania. Każda instancja może mieć niezależną pozycję, rotację,
skalę i kolor — ale wszystkie dzielą jedną geometrię i jeden
materiał.
// Przed: 10 000 wywołań rysowania
for (let i = 0; i < 10000; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(Math.random() * 100, 0, Math.random() * 100);
scene.add(mesh);
}
// Po: 1 wywołanie rysowania
const instancedMesh = new THREE.InstancedMesh(geometry, material, 10000);
const dummy = new THREE.Object3D();
for (let i = 0; i < 10000; i++) {
dummy.position.set(Math.random() * 100, 0, Math.random() * 100);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);
Aktualizacja transformacji instancji w każdej klatce
Dla animowanych instancji aktualizuj macierz w każdej klatce i oznacz ją jako „brudną":
function animate() {
for (let i = 0; i < COUNT; i++) {
dummy.position.x = positions[i].x + Math.sin(time + i) * 0.1;
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true; // kluczowe!
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
Pułapka: zapomnienie o
needsUpdate = true oznacza, że GPU nigdy nie zobaczy
nowych macierzy. Instancje będą wyglądać na zamrożone, mimo że
pętla JavaScript działa. To najczęstszy błąd związany z
InstancedMesh.
Kolor na instancję
const color = new THREE.Color();
instancedMesh.instanceColor = new THREE.InstancedBufferAttribute(
new Float32Array(COUNT * 3), 3
);
for (let i = 0; i < COUNT; i++) {
color.setHSL(i / COUNT, 1.0, 0.5);
instancedMesh.setColorAt(i, color);
}
instancedMesh.instanceColor.needsUpdate = true;
Kiedy NIE używać InstancedMesh
- Gdy instancje wymagają różnych materiałów (shaderów) — użyj wielu InstancedMesh, po jednym na materiał
-
Gdy potrzebujesz precyzyjnego raycastingu per instancja —
instancedMesh.computeBoundingSphere()pomaga, ale grupuje granice - Gdy liczba instancji zmienia się w każdej klatce — lepiej zastosować podejście z pulą i ukrywać nieużywane instancje poza ekranem