Правильне звільнення об'єктів Three.js (без витоків пам'яті)

Геометрія, матеріал, текстура і рендерер — усе це витікає GPU-пам'ять, якщо не викликати dispose() правильно. Повний контрольний список, яким я користуюся перед очищенням кожної симуляції.

Чому Three.js витікає GPU-пам'ять

Збирач сміття JavaScript автоматично керує пам'яттю CPU. Але об'єкти Three.js також утримують ресурси GPU — буферні об'єкти, текстури, фреймбуфери — якими керують через API WebGL. Збирач сміття не знає про них. Коли ви відкидаєте Mesh, видаляючи посилання JavaScript, пам'ять GPU залишається виділеною, доки ви явно не викличете dispose().

У довготривалій симуляції, що регулярно створює й відкидає об'єкти, це накопичується в сотні мегабайтів витоку VRAM, зрештою спричиняючи збої або серйозне падіння продуктивності.

Перевірка симптомів: Відкрийте Chrome DevTools → Performance → Memory. Якщо пам'ять GPU постійно зростає під час роботи вашої симуляції (навіть коли візуально нічого не змінюється), у вас є витік.

Повний контрольний список

Геометрія

geometry.dispose(); // Звільняє буфери вершин та індексів

Матеріали

material.dispose(); // Звільняє скомпільовану шейдерну програму

Текстури

// Ручне звільнення текстур
material.map?.dispose();
material.normalMap?.dispose();
material.roughnessMap?.dispose();
material.envMap?.dispose();
// ... усі властивості map

// Або за допомогою обходу
scene.traverse((obj) => {
  if (obj.material) {
    Object.values(obj.material).forEach(val => {
      if (val?.isTexture) val.dispose();
    });
  }
});

Цілі рендерингу

renderTarget.dispose(); // Звільняє FBO + прикріплені текстури

Повний патерн демонтажу

function disposeMesh(mesh) {
  mesh.geometry?.dispose();
  if (Array.isArray(mesh.material)) {
    mesh.material.forEach(disposeMaterial);
  } else {
    disposeMaterial(mesh.material);
  }
}

function disposeMaterial(mat) {
  if (!mat) return;
  mat.dispose();
  // Звільнення всіх текстур map
  ['map','normalMap','roughnessMap','metalnessMap',
   'aoMap','emissiveMap','envMap','lightMap',
   'bumpMap','displacementMap','alphaMap']
    .forEach(key => mat[key]?.dispose());
}

// Демонтаж сцени
scene.traverse((obj) => {
  if (obj.isMesh) disposeMesh(obj);
});
renderer.dispose();
renderer.forceContextLoss();

InstancedMesh

InstancedMesh — це просто Mesh — звільняйте його геометрію та матеріал так само. Атрибути буферів instanceMatrix та instanceColor звільняються при звільненні геометрії.

Порада профі: Викликайте renderer.info.reset() між сценами і виводьте в консоль renderer.info.memory, щоб перевірити, що лічильники геометрії та текстур обнуляються після вашого демонтажу. Якщо ні — ви щось пропустили в dispose.