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();
}
四、高级加载技术
- 纹理与材质处理:
// 加载纹理
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;
}
});
- 异步加载管理器:
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);
});
五、高级导出技术
- 自定义导出内容:
// 只导出选定的对象
const selection = new THREE.Group();
selection.add(model1);
selection.add(model2);
exporter.parse(selection, (result) => {
saveArrayBuffer(result, 'selected_models.glb');
});
- 导出动画数据:
// 导出包含动画的模型
const options = {
binary: true,
animations: mixer.clipAction(gltf.animations[0]).getClip() // 导出特定动画
};
exporter.parse(model, (result) => {
saveArrayBuffer(result, 'animated_model.glb');
}, options);
六、性能优化与故障排除
- 模型优化建议:
-
- 使用 GLTF/GLB 格式(官方推荐的 Web 格式)
-
- 压缩纹理(使用 PNG/JPG 或 ASTC/ETC 格式)
-
- 简化复杂模型(减少多边形数量)
-
- 使用实例化(InstancedMesh)处理大量重复对象
- 常见问题解决方案:
-
- 模型不显示:检查路径、缩放比例和材质设置
-
- 纹理加载失败:检查路径和跨域资源共享(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>
八、最佳实践总结
- 选择合适的模型格式:
-
- GLTF/GLB:Web 首选格式,支持动画、材质和纹理
-
- OBJ:简单几何模型,不支持材质和动画
-
- FBX:复杂模型,支持动画和材质
- 优化加载体验:
-
- 使用加载进度指示器
-
- 实现资源预加载
-
- 使用 CDN 加速静态资源
- 导出注意事项:
-
- 导出前清理不需要的对象
-
- 确保纹理路径正确
-
- 测试导出的模型是否能正确加载
通过掌握这些技术,你可以在 Three.js 应用中实现高效的模型加载与导出功能,为用户提供出色的 3D 体验。