Three.js 完全学习指南(二十二)最佳实践

288 阅读2分钟

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

最佳实践

在 Three.js 开发中,遵循最佳实践可以帮助我们构建更高质量、更易维护的应用。本章将介绍一些重要的最佳实践。

代码组织

1. 项目结构

最佳实践示例

图 22.1: 最佳实践示例

// 项目结构示例
project/
├── src/
│   ├── core/
│   │   ├── Scene.js
│   │   ├── Camera.js
│   │   └── Renderer.js
│   ├── objects/
│   │   ├── geometries/
│   │   ├── materials/
│   │   └── lights/
│   ├── utils/
│   │   ├── loaders/
│   │   ├── helpers/
│   │   └── controls/
│   └── main.js
├── assets/
│   ├── models/
│   ├── textures/
│   └── sounds/
└── index.html

2. 模块化设计

// 场景管理器
class SceneManager {
    constructor() {
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.controls = null;
        this.objects = new Map();
    }

    init() {
        this.setupRenderer();
        this.setupCamera();
        this.setupControls();
        this.setupLights();
        this.setupEventListeners();
    }

    setupRenderer() {
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        document.body.appendChild(this.renderer.domElement);
    }

    setupCamera() {
        this.camera.position.set(0, 5, 10);
        this.camera.lookAt(0, 0, 0);
    }

    setupControls() {
        this.controls = new OrbitControls(this.camera, this.renderer.domElement);
        this.controls.enableDamping = true;
    }

    setupLights() {
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
        this.scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(5, 5, 5);
        directionalLight.castShadow = true;
        this.scene.add(directionalLight);
    }

    setupEventListeners() {
        window.addEventListener('resize', () => this.onWindowResize());
    }

    onWindowResize() {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }

    addObject(name, object) {
        this.objects.set(name, object);
        this.scene.add(object);
    }

    removeObject(name) {
        const object = this.objects.get(name);
        if (object) {
            this.scene.remove(object);
            this.objects.delete(name);
        }
    }

    update() {
        this.controls.update();
    }

    render() {
        this.renderer.render(this.scene, this.camera);
    }

    dispose() {
        this.objects.forEach(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();
                }
            }
        });
        this.renderer.dispose();
    }
}

错误处理

1. 错误管理器

// 错误管理器
class ErrorManager {
    constructor() {
        this.errors = [];
        this.maxErrors = 100;
    }

    handleError(error, context = '') {
        const errorInfo = {
            message: error.message,
            stack: error.stack,
            context,
            timestamp: new Date().toISOString()
        };

        this.errors.push(errorInfo);
        if (this.errors.length > this.maxErrors) {
            this.errors.shift();
        }

        console.error(`[${context}] ${error.message}`, error);
    }

    getErrors() {
        return this.errors;
    }

    clearErrors() {
        this.errors = [];
    }
}

2. 资源加载错误处理

// 资源加载管理器
class ResourceLoader {
    constructor(errorManager) {
        this.errorManager = errorManager;
        this.loadingManager = new THREE.LoadingManager(
            // 加载完成回调
            () => console.log('Loading complete'),
            // 加载进度回调
            (url, itemsLoaded, itemsTotal) => {
                console.log(`Loading file: ${url} (${itemsLoaded}/${itemsTotal})`);
            },
            // 加载错误回调
            (url) => {
                this.errorManager.handleError(
                    new Error(`Error loading ${url}`),
                    'ResourceLoader'
                );
            }
        );
    }

    loadTexture(url) {
        return new Promise((resolve, reject) => {
            const loader = new THREE.TextureLoader(this.loadingManager);
            loader.load(
                url,
                (texture) => resolve(texture),
                undefined,
                (error) => reject(error)
            );
        });
    }

    loadModel(url) {
        return new Promise((resolve, reject) => {
            const loader = new THREE.GLTFLoader(this.loadingManager);
            loader.load(
                url,
                (gltf) => resolve(gltf),
                undefined,
                (error) => reject(error)
            );
        });
    }
}

性能优化

1. 性能监控

// 性能监控器
class PerformanceMonitor {
    constructor() {
        this.metrics = {
            fps: 0,
            frameTime: 0,
            memory: 0,
            drawCalls: 0,
            triangles: 0
        };
        this.frameCount = 0;
        this.lastTime = performance.now();
    }

    update(renderer) {
        const currentTime = performance.now();
        const deltaTime = currentTime - this.lastTime;

        this.frameCount++;

        if (deltaTime >= 1000) {
            this.metrics.fps = Math.round((this.frameCount * 1000) / deltaTime);
            this.metrics.frameTime = deltaTime / this.frameCount;
            this.metrics.drawCalls = renderer.info.render.calls;
            this.metrics.triangles = renderer.info.render.triangles;

            if (performance.memory) {
                this.metrics.memory = performance.memory.usedJSHeapSize / 1048576;
            }

            this.frameCount = 0;
            this.lastTime = currentTime;
        }
    }

    getMetrics() {
        return this.metrics;
    }
}

2. 资源管理

// 资源管理器
class ResourceManager {
    constructor() {
        this.resources = new Map();
        this.loadingPromises = new Map();
    }

    async loadResource(key, loader) {
        if (this.resources.has(key)) {
            return this.resources.get(key);
        }

        if (this.loadingPromises.has(key)) {
            return this.loadingPromises.get(key);
        }

        const promise = loader().catch(error => {
            this.loadingPromises.delete(key);
            throw error;
        });

        this.loadingPromises.set(key, promise);
        const resource = await promise;
        this.resources.set(key, resource);
        this.loadingPromises.delete(key);

        return resource;
    }

    getResource(key) {
        return this.resources.get(key);
    }

    dispose() {
        this.resources.forEach(resource => {
            if (resource.dispose) {
                resource.dispose();
            }
        });
        this.resources.clear();
        this.loadingPromises.clear();
    }
}

实战:最佳实践应用

让我们创建一个完整的应用示例:

// 创建应用
class ThreeJSApp {
    constructor() {
        this.sceneManager = new SceneManager();
        this.errorManager = new ErrorManager();
        this.resourceLoader = new ResourceLoader(this.errorManager);
        this.resourceManager = new ResourceManager();
        this.performanceMonitor = new PerformanceMonitor();
    }

    async init() {
        try {
            // 初始化场景
            this.sceneManager.init();

            // 加载资源
            await this.loadResources();

            // 创建场景对象
            this.createSceneObjects();

            // 开始动画循环
            this.animate();
        } catch (error) {
            this.errorManager.handleError(error, 'ThreeJSApp.init');
        }
    }

    async loadResources() {
        try {
            // 加载纹理
            const texture = await this.resourceLoader.loadTexture('texture.jpg');
            this.resourceManager.resources.set('texture', texture);

            // 加载模型
            const model = await this.resourceLoader.loadModel('model.glb');
            this.resourceManager.resources.set('model', model);
        } catch (error) {
            this.errorManager.handleError(error, 'ThreeJSApp.loadResources');
        }
    }

    createSceneObjects() {
        // 创建地面
        const groundGeometry = new THREE.PlaneGeometry(10, 10);
        const groundMaterial = new THREE.MeshStandardMaterial({
            color: 0x808080,
            roughness: 0.8,
            metalness: 0.2
        });
        const ground = new THREE.Mesh(groundGeometry, groundMaterial);
        ground.rotation.x = -Math.PI / 2;
        ground.receiveShadow = true;
        this.sceneManager.addObject('ground', ground);

        // 添加模型
        const model = this.resourceManager.getResource('model');
        if (model) {
            this.sceneManager.addObject('model', model.scene);
        }
    }

    animate() {
        requestAnimationFrame(() => this.animate());

        try {
            // 更新场景
            this.sceneManager.update();

            // 更新性能监控
            this.performanceMonitor.update(this.sceneManager.renderer);

            // 渲染场景
            this.sceneManager.render();
        } catch (error) {
            this.errorManager.handleError(error, 'ThreeJSApp.animate');
        }
    }

    dispose() {
        this.sceneManager.dispose();
        this.resourceManager.dispose();
    }
}

// 创建并初始化应用
const app = new ThreeJSApp();
app.init().catch(error => {
    console.error('Failed to initialize app:', error);
});

// 页面卸载时清理资源
window.addEventListener('unload', () => {
    app.dispose();
});

感谢大家阅读到这里,本系列的 Three.js 学习已经结束,希望对大家有所帮助。

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!