1. Three.js GLTFLoader与Draco压缩

1.1. Draco 压缩简介

Draco 是 Google 开发的开源 3D 几何压缩库,可以显著减小 3D 模型文件大小,特别适用于 Web 传输。

1.2. 基本使用方法

1.3. 安装和引入

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';

1.4. 基本加载代码

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);
  }
);

1.5. Draco 编码工具

1.5.1. 使用 glTF-Pipeline 进行压缩

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

1.5.2. 使用 Blender 导出

  1. 安装 Blender 的 glTF 2.0 导出插件

  2. 导出时勾选 "Draco Compression" 选项

  3. 设置压缩参数:

1.6. 进阶配置

1.6.1. 自定义解码器路径管理

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);
  }
}

1.6.2. 性能优化配置

javascript

const dracoLoader = new DRACOLoader();

// 预加载解码器(可选,可以提前加载解码器)
dracoLoader.preload();

// 设置 Worker 以提高性能
dracoLoader.setWorkerLimit(4); // 使用 4 个 worker 线程

// 自定义解码器配置
dracoLoader.setDecoderConfig({
  type: 'js' // 或 'wasm',WebAssembly 通常更快
});

// 启用内存管理(大型模型时重要)
dracoLoader.dispose(); // 使用后清理内存

1.6.3. 错误处理和兼容性

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;
  }
}

1.7. Draco 压缩参数说明

1.7.1. 压缩选项

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,    // 是否统一量化
};

1.8. 最佳实践

1.8.1. 项目结构建议

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

1.8.2. 懒加载解码器

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);
  });
}

1.8.3. 渐进加载策略

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();
    }
  }
}

1.9. 常见问题解决

1.9.1. 解码器路径问题

javascript

// 确保路径正确
dracoLoader.setDecoderPath('/path/to/draco/');
// 路径应包含末尾斜杠,且目录下应有:
// draco_decoder.js
// draco_decoder.wasm
// draco_wasm_wrapper.js

1.9.2. 内存泄漏处理

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();
      }
    }
  });
}

1.9.3. 兼容性处理

javascript

// 检测浏览器支持
function supportsDraco() {
  return (
    typeof WebAssembly === 'object' &&
    (window.WebAssembly !== undefined || self.WebAssembly !== undefined)
  );
}

if (!supportsDraco()) {
  console.warn('浏览器不支持 WebAssembly,Draco 解码可能较慢');
  // 考虑加载未压缩版本
}

这些配置和实践可以帮助你更好地在 Three.js 项目中使用 Draco 压缩,平衡模型质量和加载性能。