共计 2198 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
Web 端 3D 应用常遇到高精度模型导致的性能问题。通过 Chrome DevTools 的 Memory 面板实测,一个 10MB 的 glTF 模型在移动端 Safari 上加载后,内存占用可能膨胀到原始文件的 3 - 4 倍。这种现象主要由以下因素造成:

- 顶点数据未经优化,存在大量冗余信息
- 纹理贴图未压缩,占用显存过高
- 缺乏 LOD(Level of Detail)机制,导致远距离模型仍以高模渲染
技术对比
主流轻量化算法可分为三类:
- 几何压缩算法
- Draco:Google 开源的几何压缩库,压缩率可达 70%,但解码需要额外时间
-
Meshopt:基于顶点缓存优化的算法,解码速度更快但压缩率稍低
-
纹理压缩方案
- Basis Universal:支持转码为 ASTC/DXT/ETC 等格式
-
WebP:适合非 PBR 材质的简单纹理
-
简化算法
- Quadric Error Metrics:保持模型特征的顶点合并算法
- Edge Collapse:适合机械类模型的简化
选型决策树:
- 需要极致压缩 → Draco+Meshopt 组合
- 追求实时性能 → 纯 Meshopt 方案
- 含动画的模型 → 优先保持骨骼权重完整性
核心实现
基础轻量化流程
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader';
import {DRACOLoader} from 'three/examples/jsm/loaders/DRACOLoader';
// 初始化解码器
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
export async function loadOptimizedModel(url: string) {const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
try {const gltf = await loader.loadAsync(url);
// 自动计算法线(适用于无 UV 的简单模型)gltf.scene.traverse((child) => {if (child.isMesh) {child.geometry.computeVertexNormals();
}
});
return gltf;
} catch (err) {console.error('模型加载失败:', err);
throw new Error('MODEL_LOAD_FAILED');
}
}
LOD 生成实现
import {LOD} from 'three';
import {simplifyModifier} from 'three/examples/jsm/modifiers/SimplifyModifier';
function setupModelLOD(originalMesh: THREE.Mesh, ratios: number[]) {const lod = new LOD();
// 原始模型作为最高细节层级
lod.addLevel(originalMesh.clone(), 0);
// 生成简化版本
const modifier = new simplifyModifier();
ratios.forEach((ratio, index) => {const simplified = originalMesh.clone();
simplified.geometry = modifier.modify(
simplified.geometry,
Math.floor(originalMesh.geometry.attributes.position.count * ratio)
);
lod.addLevel(simplified, (index + 1) * 10); // 距离阈值
});
return lod;
}
性能验证
测试数据(iPhone 13 Pro):
| 指标 | 原始模型 | 优化后 | 降幅 |
|---|---|---|---|
| 文件大小 | 8.7MB | 2.1MB | 75% |
| GPU 内存占用 | 310MB | 85MB | 72% |
| 平均 FPS@60fps 目标 | 48 | 58 | +21% |
避坑指南
法线贴图失真解决
当使用 Basis 压缩法线贴图时,需要修改编码方式:
// 在着色器中修正压缩后的法线
vec3 fixedNormal = texture(normalMap, uv).xyz * 2.0 - 1.0;
fixedNormal = normalize(TBN * fixedNormal);
WebWorker 解码优化
避免主线程卡顿的 Draco 解码方案:
// worker.js
self.importScripts('draco_decoder.js');
self.onmessage = (e) => {const { data, taskId} = e.data;
const decoder = new DracoDecoderModule();
// ... 解码逻辑
self.postMessage({taskId, decodedData});
};
// 主线程
const worker = new Worker('decoder.worker.js');
worker.postMessage({
data: buffer,
taskId: 'model1'
});
延伸思考
对于工程 CAD 模型,轻量化需要特殊处理:
- 保持关键尺寸的几何精度
- 孔洞、倒角等特征不能丢失
- BOM 信息需要单独存储
建议采用分段简化策略:对非关键部位使用高压缩比,对配合面保持原始精度。
正文完
发表至: 未分类
近三天内
