Poprawne usuwanie obiektów Three.js (bez wycieków pamięci)

Geometria, materiał, tekstura i renderer — wszystkie powodują wycieki pamięci GPU, jeśli nie wywołasz dispose() poprawnie. Kompletna lista kontrolna, której używam przed każdym czyszczeniem symulacji.

Dlaczego Three.js powoduje wycieki pamięci GPU

Garbage collector JavaScriptu automatycznie obsługuje pamięć CPU. Ale obiekty Three.js posiadają też zasoby GPU — obiekty buforowe, tekstury, framebuffery — zarządzane przez API WebGL. Garbage collector nic o nich nie wie. Gdy porzucasz Mesh, usuwając referencję JavaScript, pamięć GPU pozostaje zaalokowana, dopóki nie wywołasz jawnie dispose().

W długo działającej symulacji, która regularnie tworzy i porzuca obiekty, sumuje się to do setek megabajtów wyciekniętego VRAM, co ostatecznie powoduje awarie lub poważny spadek wydajności.

Sprawdzenie objawów: Otwórz Chrome DevTools → Performance → Memory. Jeśli pamięć GPU stale rośnie podczas działania symulacji (nawet gdy nic wizualnie się nie zmienia), masz wyciek.

Kompletna lista kontrolna

Geometria

geometry.dispose(); // Zwalnia bufory wierzchołków, bufory indeksów

Materiały

material.dispose(); // Zwalnia skompilowany program shadera

Tekstury

// Ręczne usuwanie tekstur
material.map?.dispose();
material.normalMap?.dispose();
material.roughnessMap?.dispose();
material.envMap?.dispose();
// ... wszystkie właściwości map

// Albo użyj przejścia po drzewie
scene.traverse((obj) => {
  if (obj.material) {
    Object.values(obj.material).forEach(val => {
      if (val?.isTexture) val.dispose();
    });
  }
});

Cele renderowania

renderTarget.dispose(); // Zwalnia FBO + dołączone tekstury

Pełny wzorzec demontażu

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();
  // Usuń wszystkie tekstury map
  ['map','normalMap','roughnessMap','metalnessMap',
   'aoMap','emissiveMap','envMap','lightMap',
   'bumpMap','displacementMap','alphaMap']
    .forEach(key => mat[key]?.dispose());
}

// Demontaż sceny
scene.traverse((obj) => {
  if (obj.isMesh) disposeMesh(obj);
});
renderer.dispose();
renderer.forceContextLoss();

InstancedMesh

InstancedMesh to po prostu Mesh — usuwaj jego geometrię i materiał w ten sam sposób. Atrybuty buforów instanceMatrix i instanceColor są zwalniane wraz z usunięciem geometrii.

Wskazówka od profesjonalisty: Wywołaj renderer.info.reset() między scenami i zaloguj renderer.info.memory, aby zweryfikować, że liczba geometrii i tekstur spada do zera po demontażu. Jeśli nie — gdzieś pominąłeś dispose.