Three.js 模型加载与导出完全指南

1,043 阅读3分钟

Three.js 是一款强大的 3D 渲染库,可帮助开发者在网页上创建沉浸式 3D 体验。本指南将深入探讨 Three.js 中模型加载与导出的核心技术,包含完整的代码实现和最佳实践。

一、Three.js 基础架构

Three.js 使用场景 (Scene)、相机 (Camera) 和渲染器 (Renderer) 构建 3D 环境:

// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加光源
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// 渲染循环
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

二、模型加载技术

Three.js 支持多种模型格式,包括 GLTF、OBJ、FBX 等。以下是加载 GLTF 模型的完整示例:

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
// 初始化加载器
const loader = new GLTFLoader();
// 加载模型
loader.load(
    'path/to/model.gltf',
    (gltf) => {
        // 成功加载回调
        const model = gltf.scene;
        
        // 调整模型属性
        model.scale.set(1, 1, 1);
        model.position.set(0, 0, 0);
        
        // 添加到场景
        scene.add(model);
        
        // 播放动画(如果有)
        if (gltf.animations && gltf.animations.length) {
            const mixer = new THREE.AnimationMixer(model);
            const action = mixer.clipAction(gltf.animations[0]);
            action.play();
            
            // 在动画循环中更新混合器
            function animate() {
                requestAnimationFrame(animate);
                const delta = clock.getDelta();
                mixer.update(delta);
                renderer.render(scene, camera);
            }
            animate();
        }
    },
    (progress) => {
        // 加载进度回调
        const percent = (progress.loaded / progress.total) * 100;
        console.log(`加载进度: ${percent}%`);
    },
    (error) => {
        // 错误处理
        console.error('加载模型时出错:', error);
    }
);

三、模型导出技术

Three.js 提供多种导出模型的方法,以下是导出为 GLTF 格式的实现:

import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
// 创建导出函数
function exportGLTF() {
    // 创建导出器实例
    const exporter = new GLTFExporter();
    
    // 准备要导出的对象(可以是整个场景或特定模型)
    const objectToExport = scene; // 或者特定模型: model
    
    // 导出参数
    const options = {
        binary: true, // 导出为二进制格式 (.glb)
        trs: false,   // 不优化变换
        onlyVisible: true, // 只导出可见对象
        embedImages: true, // 嵌入纹理
        maxTextureSize: 4096 // 最大纹理尺寸
    };
    
    // 执行导出
    exporter.parse(
        objectToExport,
        (result) => {
            if (result instanceof ArrayBuffer) {
                saveArrayBuffer(result, 'model.glb');
            } else {
                const output = JSON.stringify(result, null, 2);
                console.log(output);
                saveString(output, 'model.gltf');
            }
        },
        (error) => {
            console.error('导出模型时出错:', error);
        },
        options
    );
}
// 辅助函数:保存为文件
function saveArrayBuffer(buffer, fileName) {
    save(new Blob([buffer], { type: 'application/octet-stream' }), fileName);
}
function saveString(text, fileName) {
    save(new Blob([text], { type: 'text/plain' }), fileName);
}
// 使用浏览器API保存文件
function save(blob, fileName) {
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = fileName;
    link.click();
}

四、高级加载技术

  1. 纹理与材质处理
// 加载纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/texture.jpg');
// 应用纹理到材质
const material = new THREE.MeshStandardMaterial({ map: texture });
// 为模型设置材质
model.traverse((child) => {
    if (child.isMesh) {
        child.material = material;
    }
});
  1. 异步加载管理器
import { LoadingManager } from 'three';
// 创建加载管理器
const manager = new LoadingManager();
// 设置加载完成回调
manager.onLoad = () => {
    console.log('所有资源加载完成');
};
// 设置加载进度回调
manager.onProgress = (url, loaded, total) => {
    console.log(`加载: ${url}, ${loaded}/${total}`);
};
// 使用管理器加载资源
const loader = new GLTFLoader(manager);
loader.load('path/to/model.gltf', (gltf) => {
    scene.add(gltf.scene);
});

五、高级导出技术

  1. 自定义导出内容
// 只导出选定的对象
const selection = new THREE.Group();
selection.add(model1);
selection.add(model2);
exporter.parse(selection, (result) => {
    saveArrayBuffer(result, 'selected_models.glb');
});
  1. 导出动画数据
// 导出包含动画的模型
const options = {
    binary: true,
    animations: mixer.clipAction(gltf.animations[0]).getClip() // 导出特定动画
};
exporter.parse(model, (result) => {
    saveArrayBuffer(result, 'animated_model.glb');
}, options);

六、性能优化与故障排除

  1. 模型优化建议
    • 使用 GLTF/GLB 格式(官方推荐的 Web 格式)
    • 压缩纹理(使用 PNG/JPG 或 ASTC/ETC 格式)
    • 简化复杂模型(减少多边形数量)
    • 使用实例化(InstancedMesh)处理大量重复对象
  1. 常见问题解决方案
    • 模型不显示:检查路径、缩放比例和材质设置
    • 纹理加载失败:检查路径和跨域资源共享(CORS)设置
    • 导出文件过大:优化纹理尺寸,使用二进制格式(.glb)

七、完整工作流示例

下面是一个完整的 Three.js 模型加载与导出应用示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 模型加载与导出示例</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/loaders/GLTFLoader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/exporters/GLTFExporter.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/controls/OrbitControls.js"></script>
    <style>
        body { margin: 0; overflow: hidden; }
        #container { width: 100%; height: 100vh; }
        button { position: absolute; top: 10px; right: 10px; z-index: 10; }
        #progress { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 20; }
    </style>
</head>
<body>
    <div id="container"></div>
    <button id="exportButton">导出模型</button>
    <div id="progress">加载中...</div>
    <script>
        // 初始化场景、相机和渲染器
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xf0f0f0);
        
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 5;
        
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.getElementById('container').appendChild(renderer.domElement);
        
        // 添加光源
        const ambientLight = new THREE.AmbientLight(0x404040);
        scene.add(ambientLight);
        
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
        directionalLight.position.set(1, 1, 1);
        scene.add(directionalLight);
        
        // 添加控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        
        // 加载器
        const loader = new GLTFLoader();
        const progressElement = document.getElementById('progress');
        let model;
        
        // 加载模型
        loader.load(
            'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
            (gltf) => {
                model = gltf.scene;
                scene.add(model);
                
                // 调整模型位置和缩放
                model.scale.set(0.01, 0.01, 0.01);
                
                // 隐藏进度指示器
                progressElement.style.display = 'none';
                
                console.log('模型加载成功');
            },
            (progress) => {
                // 更新进度
                const percent = Math.round((progress.loaded / progress.total) * 100);
                progressElement.textContent = `加载中: ${percent}%`;
            },
            (error) => {
                console.error('加载模型时出错:', error);
                progressElement.textContent = '加载失败';
            }
        );
        
        // 导出按钮事件
        document.getElementById('exportButton').addEventListener('click', () => {
            if (!model) {
                alert('请先加载模型');
                return;
            }
            
            // 创建导出器
            const exporter = new GLTFExporter();
            
            // 导出模型
            exporter.parse(
                model,
                (result) => {
                    if (result instanceof ArrayBuffer) {
                        saveArrayBuffer(result, 'exported_model.glb');
                    } else {
                        const output = JSON.stringify(result, null, 2);
                        saveString(output, 'exported_model.gltf');
                    }
                },
                (error) => {
                    console.error('导出模型时出错:', error);
                    alert('导出失败');
                }
            );
        });
        
        // 辅助函数
        function saveArrayBuffer(buffer, fileName) {
            save(new Blob([buffer], { type: 'application/octet-stream' }), fileName);
        }
        
        function saveString(text, fileName) {
            save(new Blob([text], { type: 'text/plain' }), fileName);
        }
        
        function save(blob, fileName) {
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = fileName;
            link.click();
        }
        
        // 响应窗口大小变化
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        
        // 渲染循环
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>

八、最佳实践总结

  1. 选择合适的模型格式
    • GLTF/GLB:Web 首选格式,支持动画、材质和纹理
    • OBJ:简单几何模型,不支持材质和动画
    • FBX:复杂模型,支持动画和材质
  1. 优化加载体验
    • 使用加载进度指示器
    • 实现资源预加载
    • 使用 CDN 加速静态资源
  1. 导出注意事项
    • 导出前清理不需要的对象
    • 确保纹理路径正确
    • 测试导出的模型是否能正确加载

通过掌握这些技术,你可以在 Three.js 应用中实现高效的模型加载与导出功能,为用户提供出色的 3D 体验。