Draco 是 Google 开发的开源 3D 几何压缩库,可以显著减小 3D 模型文件大小,特别适用于 Web 传输。
javascript
// 安装
npm install three
// 或者使用 CDN
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/libs/draco/gltf/draco_decoder.js"></script>
// ES6 模块引入
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
javascript
// 创建加载器
const loader = new GLTFLoader();
// 创建 Draco 加载器并配置
const dracoLoader = new DRACOLoader();
// 设置解码器路径(重要!)
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
// 或者使用本地路径
// dracoLoader.setDecoderPath('/path/to/draco/decoder/');
// 将 DracoLoader 分配给 GLTFLoader
loader.setDRACOLoader(dracoLoader);
// 加载 Draco 压缩的模型
loader.load(
'model.glb', // 或 model.gltf
function (gltf) {
scene.add(gltf.scene);
console.log('模型加载成功');
},
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
function (error) {
console.error('加载错误:', error);
}
);
bash
# 安装 glTF-Pipeline
npm install -g gltf-pipeline
# 将 glTF 转换为 Draco 压缩的 glTF
gltf-pipeline -i input.gltf -o output.gltf -d
# 转换为 Draco 压缩的 GLB(二进制格式)
gltf-pipeline -i input.gltf -o output.glb -d
# 指定压缩级别(0-10,越高压缩率越大)
gltf-pipeline -i input.gltf -o output.gltf -d --draco.compressionLevel 10
# 只压缩几何数据,不压缩其他属性
gltf-pipeline -i input.gltf -o output.gltf -d --draco.quantizePositionBits 14
安装 Blender 的 glTF 2.0 导出插件
导出时勾选 "Draco Compression" 选项
设置压缩参数:
Compression Level (0-10)
Quantize Position/Normal/TexCoord 等
javascript
class DracoLoaderManager {
constructor() {
this.dracoLoader = new DRACOLoader();
this.gltfLoader = new GLTFLoader();
// 检测并设置最佳解码器路径
this.setupDecoderPath();
}
setupDecoderPath() {
// 尝试多种解码器源
const paths = [
'/libs/draco/', // 本地
'https://www.gstatic.com/draco/v1/decoders/', // Google CDN
'https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/libs/draco/gltf/' // jsDelivr
];
this.dracoLoader.setDecoderPath(paths[0]);
this.gltfLoader.setDRACOLoader(this.dracoLoader);
}
loadModel(url, onLoad, onProgress, onError) {
return this.gltfLoader.load(url, onLoad, onProgress, onError);
}
}
javascript
const dracoLoader = new DRACOLoader();
// 预加载解码器(可选,可以提前加载解码器)
dracoLoader.preload();
// 设置 Worker 以提高性能
dracoLoader.setWorkerLimit(4); // 使用 4 个 worker 线程
// 自定义解码器配置
dracoLoader.setDecoderConfig({
type: 'js' // 或 'wasm',WebAssembly 通常更快
});
// 启用内存管理(大型模型时重要)
dracoLoader.dispose(); // 使用后清理内存
javascript
async function loadDracoModel(modelPath) {
try {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
// 设置备选解码器路径
const decoderPath = await getBestDecoderPath();
dracoLoader.setDecoderPath(decoderPath);
loader.setDRACOLoader(dracoLoader);
// 设置超时
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('加载超时')), 30000);
});
const loadPromise = new Promise((resolve, reject) => {
loader.load(modelPath, resolve, null, reject);
});
const gltf = await Promise.race([loadPromise, timeoutPromise]);
// 加载后清理
dracoLoader.dispose();
return gltf;
} catch (error) {
console.error('加载 Draco 模型失败:', error);
// 尝试加载未压缩版本作为降级方案
if (error.message.includes('draco')) {
console.log('尝试加载未压缩版本...');
return loadUncompressedModel(modelPath.replace('.glb', '-uncompressed.glb'));
}
throw error;
}
}
javascript
// 使用 gltf-pipeline 时可以设置的参数
const dracoOptions = {
compressionLevel: 7, // 压缩级别 0-10
quantizePositionBits: 14, // 位置精度 1-30
quantizeNormalBits: 10, // 法线精度 1-30
quantizeTexcoordBits: 12, // UV 坐标精度
quantizeColorBits: 8, // 颜色精度
quantizeGenericBits: 12, // 通用属性精度
unifiedQuantization: false, // 是否统一量化
};
text
project/
├── src/
│ └── utils/
│ └── ModelLoader.js
├── public/
│ ├── models/
│ │ ├── model-draco.glb
│ │ └── model-uncompressed.glb (备用)
│ └── libs/
│ └── draco/
│ ├── draco_decoder.js
│ ├── draco_decoder.wasm
│ └── draco_wasm_wrapper.js
javascript
let dracoLoader = null;
async function loadModelWithDraco(modelUrl) {
if (!dracoLoader) {
// 动态导入 DracoLoader
const { DRACOLoader } = await import(
'three/examples/jsm/loaders/DRACOLoader'
);
dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/libs/draco/');
}
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
return new Promise((resolve, reject) => {
loader.load(modelUrl, resolve, null, reject);
});
}
javascript
class ProgressiveDracoLoader {
constructor() {
this.priorityQueue = [];
this.isLoading = false;
}
addToQueue(modelInfo) {
this.priorityQueue.push(modelInfo);
this.priorityQueue.sort((a, b) => b.priority - a.priority);
this.processQueue();
}
async processQueue() {
if (this.isLoading || this.priorityQueue.length === 0) return;
this.isLoading = true;
const modelInfo = this.priorityQueue.shift();
try {
const gltf = await this.loadSingleModel(modelInfo.url);
modelInfo.onSuccess(gltf);
} catch (error) {
modelInfo.onError(error);
} finally {
this.isLoading = false;
this.processQueue();
}
}
}
javascript
// 确保路径正确
dracoLoader.setDecoderPath('/path/to/draco/');
// 路径应包含末尾斜杠,且目录下应有:
// draco_decoder.js
// draco_decoder.wasm
// draco_wasm_wrapper.js
javascript
// 使用后清理
function cleanup() {
if (dracoLoader) {
dracoLoader.dispose();
dracoLoader = null;
}
// 清理 Three.js 资源
scene.traverse(object => {
if (object.geometry) object.geometry.dispose();
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(m => m.dispose());
} else {
object.material.dispose();
}
}
});
}
javascript
// 检测浏览器支持
function supportsDraco() {
return (
typeof WebAssembly === 'object' &&
(window.WebAssembly !== undefined || self.WebAssembly !== undefined)
);
}
if (!supportsDraco()) {
console.warn('浏览器不支持 WebAssembly,Draco 解码可能较慢');
// 考虑加载未压缩版本
}
这些配置和实践可以帮助你更好地在 Three.js 项目中使用 Draco 压缩,平衡模型质量和加载性能。