Three.js不会自动释放WebGL资源,必须手动管理:
javascript
// 清理几何体
geometry.dispose();
// 清理材质
material.dispose();
// 清理纹理
texture.dispose();
// 清理渲染器(重置WebGL上下文)
renderer.dispose();
javascript
function cleanupScene(scene, renderer) {
scene.traverse((object) => {
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(material => material.dispose());
} else {
object.material.dispose();
}
}
if (object.texture) {
object.texture.dispose();
}
});
// 清空场景
while(scene.children.length > 0) {
scene.remove(scene.children[0]);
}
// 可选:清除renderer缓存
renderer.forceContextLoss();
}
javascript
// ❌ 错误的做法
function createTemporaryObject() {
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 即使从场景移除,geometry/material仍被引用
scene.remove(mesh);
// 需要手动释放!
// geometry.dispose(); // 忘记调用
// material.dispose(); // 忘记调用
}
javascript
// Three.js内部缓存可能导致内存泄漏
THREE.Cache.enabled = false; // 谨慎使用,可能影响性能
// 清除特定缓存
THREE.Cache.remove(key);
javascript
// ❌ 错误的做法
function createTemporaryObject() {
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 即使从场景移除,geometry/material仍被引用
scene.remove(mesh);
// 需要手动释放!
// geometry.dispose(); // 忘记调用
// material.dispose(); // 忘记调用
}
javascript
// Three.js内部缓存可能导致内存泄漏
THREE.Cache.enabled = false; // 谨慎使用,可能影响性能
// 清除特定缓存
THREE.Cache.remove(key);
javascript
// 监控WebGL内存
console.log(renderer.info.memory);
// 输出信息包括:
// geometries: 几何体数量
// textures: 纹理数量
// programs:着色器程序数量
Performance 面板记录内存使用
Memory 面板进行堆快照
使用"Performance monitor"面板
javascript
// 复用几何体和材质
const geometries = {};
const materials = {};
function getGeometry(type, size) {
const key = `${type}_${size}`;
if (!geometries[key]) {
geometries[key] = createGeometry(type, size);
}
return geometries[key].clone(); // 注意:clone()创建新实例
}
javascript
class ResourceManager {
constructor() {
this.resources = new Map();
}
loadTexture(url) {
if (this.resources.has(url)) {
return this.resources.get(url);
}
const texture = new THREE.TextureLoader().load(url, () => {
texture.isLoaded = true;
});
this.resources.set(url, texture);
return texture;
}
cleanupUnused() {
for (const [url, texture] of this.resources) {
if (!texture.isUsed && texture.isLoaded) {
texture.dispose();
this.resources.delete(url);
}
}
}
}
javascript
class LODManager {
constructor(scene, camera) {
this.scene = scene;
this.camera = camera;
this.visibleObjects = new Set();
}
update() {
const tempVisible = new Set();
// 视锥体剔除
const frustum = new THREE.Frustum();
const matrix = new THREE.Matrix4()
.multiplyMatrices(
this.camera.projectionMatrix,
this.camera.matrixWorldInverse
);
frustum.setFromProjectionMatrix(matrix);
this.scene.traverse((obj) => {
if (obj.geometry && frustum.intersectsObject(obj)) {
tempVisible.add(obj);
if (!this.visibleObjects.has(obj)) {
obj.visible = true;
}
}
});
// 隐藏不可见对象
for (const obj of this.visibleObjects) {
if (!tempVisible.has(obj)) {
obj.visible = false;
}
}
this.visibleObjects = tempVisible;
}
}
javascript
// 使用WeakMap存储临时引用
const weakReferences = new WeakMap();
// 避免循环引用
class GameObject {
constructor() {
this.mesh = null;
this.animations = [];
}
destroy() {
// 打破循环引用
this.animations.forEach(anim => anim.target = null);
this.animations = null;
if (this.mesh) {
this.mesh.geometry.dispose();
this.mesh.material.dispose();
this.mesh.parent?.remove(this.mesh);
this.mesh = null;
}
}
}
javascript
class MemoryManager {
constructor(interval = 30000) { // 30秒清理一次
this.interval = interval;
this.startCleanupCycle();
}
startCleanupCycle() {
setInterval(() => {
this.cleanupUnusedResources();
this.forceGarbageCollection();
}, this.interval);
}
cleanupUnusedResources() {
// 清理未使用的纹理
renderer.getContext().flush();
}
forceGarbageCollection() {
/** 清理不再使用的资源
geometry.dispose();
material.dispose();
texture.dispose();
**/
}
}
javascript
import Stats from 'stats.js';
const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms, 2: mb
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// 渲染逻辑
stats.end();
}
定期进行堆快照比较
查找保留的Three.js对象
检查Detached DOM trees
始终在移除对象时调用dispose()
定期监控renderer.info.memory
避免在动画循环中创建新对象
使用对象池复用资源
及时移除事件监听器
对于大型场景,实现分块加载/卸载
通过遵循这些原则,你可以显著减少Three.js应用的内存占用,避免内存泄漏,确保应用长期稳定运行。