1. Three.js 资源管理与加载器

Three.js 提供了多种资源管理和加载器解决方案,以下是主要方法和工具:

内置加载器

Three.js 内置了多种常用格式的加载器:

1.1. 基本加载器

javascript

// 纹理加载
const textureLoader = new THREE.TextureLoader();
textureLoader.load('texture.jpg', (texture) => {
    material.map = texture;
});

// 设置回调
textureLoader.load(
    'texture.jpg',
    (texture) => { /* 加载成功 */ },
    (progress) => { /* 加载进度 */ },
    (error) => { /* 加载失败 */ }
);

1.2. 其他内置加载器

javascript

// GLTF加载器(需单独引入)
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const gltfLoader = new GLTFLoader();

// OBJ加载器
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';

// FBX加载器
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';

// 字体加载器
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';

资源管理器(LoadingManager)

1.3. 基本使用

javascript

const manager = new THREE.LoadingManager();

// 设置回调
manager.onStart = (url, itemsLoaded, itemsTotal) => {
    console.log('开始加载:', itemsLoaded, '/', itemsTotal);
};

manager.onLoad = () => {
    console.log('所有资源加载完成');
};

manager.onProgress = (url, itemsLoaded, itemsTotal) => {
    console.log(`加载进度: ${itemsLoaded}/${itemsTotal}`);
    updateProgressBar(itemsLoaded / itemsTotal);
};

manager.onError = (url) => {
    console.error('加载失败:', url);
};

// 使用管理器
const loader = new THREE.TextureLoader(manager);

1.4. 多资源加载管理

javascript

class ResourceManager {
    constructor() {
        this.manager = new THREE.LoadingManager();
        // 存储加载完成的资源
        this.resources = new Map();
        this.setupManager();
    }

    setupManager() {
        this.manager.onStart = this.onStart.bind(this);
        this.manager.onProgress = this.onProgress.bind(this);
        this.manager.onLoad = this.onLoad.bind(this);
        this.manager.onError = this.onError.bind(this);
    }

    async loadResources(resourceList) {
        const promises = resourceList.map(item => {
            return new Promise((resolve, reject) => {
                const loader = this.getLoader(item.type);
                loader.load(
                    item.url,
                    (resource) => {
                        this.resources.set(item.name, resource);
                        resolve(resource);
                    },
                    undefined,
                    (error) => reject(error)
                );
            });
        });

        return Promise.all(promises);
    }
    // 多资源加载器的封装
    getLoader(type) {
        const loaders = {
            texture: new THREE.TextureLoader(this.manager),
            gltf: new GLTFLoader(this.manager),
            cubeTexture: new THREE.CubeTextureLoader(this.manager)
        };
        return loaders[type];
    }
}

批量加载与Promise封装

1.5. Promise封装加载器

javascript

function loadTexture(url) {
    return new Promise((resolve, reject) => {
        new THREE.TextureLoader().load(url, resolve, undefined, reject);
    });
}

function loadGLTF(url) {
    return new Promise((resolve, reject) => {
        new GLTFLoader().load(url, resolve, undefined, reject);
    });
}

// 批量加载
async function loadAllResources() {
    try {
        const [texture, model] = await Promise.all([
            loadTexture('texture.jpg'),
            loadGLTF('model.glb')
        ]);
        return { texture, model };
    } catch (error) {
        console.error('资源加载失败:', error);
    }
}

1.6. 高级资源管理器

javascript

class AdvancedResourceManager {
    constructor() {
        this.cache = new Map();
        this.loaders = new Map();
        this.queue = [];
        this.loading = false;

        this.registerDefaultLoaders();
    }

    registerDefaultLoaders() {
        this.registerLoader('texture', THREE.TextureLoader);
        this.registerLoader('gltf', GLTFLoader);
        this.registerLoader('audio', THREE.AudioLoader);
    }

    registerLoader(type, LoaderClass) {
        this.loaders.set(type, LoaderClass);
    }

    addToQueue(type, name, url, options = {}) {
        this.queue.push({ type, name, url, options });
    }

    async load() {
        this.loading = true;
        const promises = this.queue.map(item => this.loadSingle(item));

        try {
            const results = await Promise.all(promises);
            this.loading = false;
            return results;
        } catch (error) {
            this.loading = false;
            throw error;
        }
    }

    async loadSingle({ type, name, url, options }) {
        // 检查缓存
        if (this.cache.has(name)) {
            return this.cache.get(name);
        }

        const LoaderClass = this.loaders.get(type);
        if (!LoaderClass) {
            throw new Error(`未注册的加载器类型: ${type}`);
        }

        const loader = new LoaderClass();
        const resource = await new Promise((resolve, reject) => {
            loader.load(url, resolve, options.onProgress, reject);
        });

        this.cache.set(name, resource);
        return resource;
    }
}

进度条与用户界面

javascript

class LoadingUI {
    constructor() {
        this.progressBar = document.getElementById('progress-bar');
        this.progressText = document.getElementById('progress-text');
        this.loadingScreen = document.getElementById('loading-screen');
    }

    show() {
        this.loadingScreen.style.display = 'flex';
    }

    hide() {
        this.loadingScreen.style.display = 'none';
    }

    updateProgress(loaded, total) {
        const percent = (loaded / total * 100).toFixed(1);
        this.progressBar.style.width = `${percent}%`;
        this.progressText.textContent = `${percent}% (${loaded}/${total})`;
    }
}

// 使用示例
const loadingUI = new LoadingUI();
const manager = new THREE.LoadingManager();

manager.onProgress = (url, loaded, total) => {
    loadingUI.updateProgress(loaded, total);
};

manager.onLoad = () => {
    setTimeout(() => loadingUI.hide(), 500);
};

缓存策略

javascript

class ResourceCache {
    constructor() {
        this.cache = new Map();
        this.maxSize = 100; // 最大缓存数
    }

    get(key) {
        if (this.cache.has(key)) {
            const item = this.cache.get(key);
            // 更新使用时间
            item.lastUsed = Date.now();
            return item.resource;
        }
        return null;
    }

    set(key, resource) {
        // 如果缓存已满,移除最久未使用的
        if (this.cache.size >= this.maxSize) {
            this.removeOldest();
        }

        this.cache.set(key, {
            resource,
            lastUsed: Date.now(),
            size: this.calculateSize(resource)
        });
    }

    removeOldest() {
        let oldestKey = null;
        let oldestTime = Infinity;

        for (const [key, item] of this.cache) {
            if (item.lastUsed < oldestTime) {
                oldestTime = item.lastUsed;
                oldestKey = key;
            }
        }

        if (oldestKey) {
            // 释放资源
            this.disposeResource(this.cache.get(oldestKey).resource);
            this.cache.delete(oldestKey);
        }
    }

    disposeResource(resource) {
        if (resource.dispose) resource.dispose();
        if (resource.textures) {
            resource.textures.forEach(texture => texture.dispose());
        }
        if (resource.materials) {
            resource.materials.forEach(material => material.dispose());
        }
        if (resource.geometries) {
            resource.geometries.forEach(geometry => geometry.dispose());
        }
    }
}

最佳实践建议

1.7. 组织资源结构

javascript

const assets = {
    textures: {
        wall: '/textures/wall.jpg',
        floor: '/textures/floor.jpg',
        skybox: '/textures/skybox/'
    },
    models: {
        character: '/models/character.glb',
        building: '/models/building.glb'
    },
    sounds: {
        background: '/sounds/background.mp3',
        effect: '/sounds/effect.mp3'
    }
};

1.8. 错误处理与重试

javascript

async function loadWithRetry(loaderFunc, url, maxRetries = 3, delay = 1000) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await loaderFunc(url);
        } catch (error) {
            if (i === maxRetries - 1) throw error;
            console.warn(`重试加载 ${url} (${i + 1}/${maxRetries})`);
            await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
        }
    }
}

1.9. 内存管理

javascript

class MemoryManager {
    constructor() {
        this.resources = new Set();
    }

    track(resource) {
        this.resources.add(resource);
    }

    cleanup() {
        for (const resource of this.resources) {
            if (resource.dispose) resource.dispose();
            if (resource.geometry) resource.geometry.dispose();
            if (resource.material) {
                if (Array.isArray(resource.material)) {
                    resource.material.forEach(m => m.dispose());
                } else {
                    resource.material.dispose();
                }
            }
            if (resource.texture) resource.texture.dispose();
        }
        this.resources.clear();
    }
}

完整示例

javascript

// 主场景资源加载器
class SceneResourceLoader {
    constructor() {
        this.manager = new THREE.LoadingManager();
        this.loadingUI = new LoadingUI();
        this.cache = new ResourceCache();
        this.setupLoadingManager();
    }

    setupLoadingManager() {
        this.manager.onStart = () => this.loadingUI.show();
        this.manager.onProgress = (url, loaded, total) => {
            this.loadingUI.updateProgress(loaded, total);
        };
        this.manager.onLoad = () => {
            setTimeout(() => this.loadingUI.hide(), 300);
        };
        this.manager.onError = (url) => {
            console.error('Failed to load:', url);
        };
    }

    async loadSceneResources(sceneConfig) {
        const promises = [];

        // 加载纹理
        for (const [name, url] of Object.entries(sceneConfig.textures)) {
            promises.push(this.loadTexture(name, url));
        }

        // 加载模型
        for (const [name, url] of Object.entries(sceneConfig.models)) {
            promises.push(this.loadModel(name, url));
        }

        // 加载音频
        for (const [name, url] of Object.entries(sceneConfig.audio)) {
            promises.push(this.loadAudio(name, url));
        }

        const results = await Promise.all(promises);
        return this.organizeResources(results);
    }

    async loadTexture(name, url) {
        const cached = this.cache.get(name);
        if (cached) return { type: 'texture', name, data: cached };

        const texture = await new Promise((resolve, reject) => {
            new THREE.TextureLoader(this.manager)
                .load(url, resolve, undefined, reject);
        });

        texture.name = name;
        this.cache.set(name, texture);
        return { type: 'texture', name, data: texture };
    }

    // 类似的方法 loadModel, loadAudio 等
}

这些资源管理方案可以根据项目需求进行组合和扩展,以构建高效、健壮的Three.js应用资源加载系统。