Проблема: виклики малювання (draw calls)
Коли ви створюєте 10 000 об'єктів Mesh у Three.js і
додаєте їх на сцену, кожен з них під час рендерингу породжує
окремий виклик малювання. Виклик малювання — це
команда процесора, надіслана відеокарті, яка каже: прив'яжи цей
буфер, застосуй цю матрицю трансформації, намалюй цю геометрію.
Відеокарта здатна обробляти мільйони полігонів за секунду — але вона може обробити лише кілька тисяч змін стану викликів малювання за секунду. Вузьке місце на боці процесора вбиває частоту кадрів задовго до того, як ви вичерпаєте запас геометричної складності.
| К-сть об'єктів | Метод | Виклики малювання | 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 |
Рішення: InstancedMesh
InstancedMesh пакує всі трансформації екземплярів в
один буфер відеокарти й рендерить усе за один виклик малювання.
Кожен екземпляр може мати незалежну позицію, обертання, масштаб
і колір — але всі вони спільно використовують одну геометрію та
один матеріал.
// До: 10 000 викликів малювання
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);
}
// Після: 1 виклик малювання
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);
Оновлення трансформацій екземплярів щокадру
Для анімованих екземплярів оновлюйте матрицю щокадру і позначайте її «брудною»:
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; // критично важливо!
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
Пастка: Якщо забути
needsUpdate = true, відеокарта ніколи не побачить
нові матриці. Екземпляри виглядатимуть застиглими, навіть якщо
ваш цикл JavaScript виконується. Це найпоширеніша помилка при
роботі з InstancedMesh.
Колір для кожного екземпляра окремо
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;
Коли НЕ варто використовувати InstancedMesh
- Коли екземплярам потрібні різні матеріали (шейдери) — використовуйте кілька InstancedMesh, по одному на матеріал
-
Коли потрібен рейкастинг для кожного екземпляра з високою
точністю —
instancedMesh.computeBoundingSphere()допомагає, але межі обчислюються пакетно - Коли кількість змінюється щокадру — краще використати підхід із пулом і приховувати невикористані екземпляри за межами екрана