← 返回花園

Three.js 資源追蹤器模式

萌芽 軟體專案 閱讀英文版
建立於 2025年9月20日 更新於 2025年12月1日
three.js webgl memory-management

問題起源

在開發 Astro Island 架構 搭配 Three.js 場景時,我遇到一個棘手的問題:頁面切換後,GPU 記憶體不斷攀升。Chrome DevTools 的 Performance Monitor 顯示 JS heap 穩定成長,但 Three.js 本身不會自動釋放已建立的 GPU 資源。

我一開始嘗試在 dispose() 裡手動逐一清理每個 geometry、material、texture。但隨著場景複雜度提升,漏掉某個資源是遲早的事。於是我改用集中式追蹤的方式。

ResourceTracker 模式

核心想法很直觀:建立一個 tracker 物件,所有透過 Three.js 建立的 GPU 資源都先經過它註冊。銷毀時只要呼叫一次 tracker.dispose(),就能確保全部釋放。

class ResourceTracker {
  private resources = new Set<{ dispose: () => void }>();

  track<T extends { dispose: () => void }>(resource: T): T {
    this.resources.add(resource);
    return resource;
  }

  dispose(): void {
    for (const resource of this.resources) {
      resource.dispose();
    }
    this.resources.clear();
  }
}

使用方式:

const tracker = new ResourceTracker();

const geometry = tracker.track(new THREE.BoxGeometry(1, 1, 1));
const texture = tracker.track(new THREE.TextureLoader().load('/textures/diffuse.webp'));
const material = tracker.track(new THREE.MeshStandardMaterial({ map: texture }));
const mesh = new THREE.Mesh(geometry, material);

// 頁面離開時
tracker.dispose();

與 Astro 生命週期整合

在 Astro 的 View Transitions 架構下,頁面切換觸發 astro:before-swap 事件。我在這個時機點執行全部清理:

document.addEventListener('astro:before-swap', () => {
  tracker.dispose();
  renderer.dispose();
  renderer.forceContextLoss();
});

同時也要處理 webglcontextlost 事件。WebGL context 可能因為瀏覽器資源壓力被回收,這不是錯誤,而是需要優雅處理的正常情境。

實際觀察

導入 ResourceTracker 後,在頁面間來回切換 50 次,GPU 記憶體維持在穩定範圍內,不再有累積性成長。這個模式適用於任何需要動態建立/銷毀 Three.js 場景的 SPA 或 MPA 架構。

待深入

  • InstancedMesh 的 buffer 追蹤是否需要特殊處理
  • render target 的清理時機
  • Blender 到 Web 管線 中,透過 GLTFLoader 載入的模型如何自動追蹤其內部所有資源

被引用於