3D商品展示正在成为电商详情页的标配能力,一双球鞋、一件家具配上可旋转的3D模型,用户的停留时长和下单转化率都有明显提升。但真正把3D能力接入线上环境时,几乎所有团队都会撞上同一堵墙:设计师从建模软件导出的模型动辄50MB起步,移动端弱网环境下加载半分钟,用户早就滑走了。模型加载慢的本质原因只有一个——要传输和解析的数据量太大。所以要解决它,思路也必须围绕“减少数据量”展开:一是从源头压缩模型文件,二是优化加载与渲染流程。下面从这两个维度详细展开。

一、用Draco压缩从源头削减模型体积
Draco是Google开源的3D图形数据压缩库,专门针对网格(mesh)数据设计。它的工作原理是对几何体的顶点坐标、法线、UV等属性进行量化编码和熵压缩。简单说,它把浮点数精度做有损压缩,比如原本用32位浮点数存储的顶点坐标,量化到更少的位数,再用预测编码消除相邻顶点之间的冗余信息。经过压缩后,模型体积通常能降到原来的5%到15%,而视觉上几乎看不出差异。
Draco压缩的另一个优势是它已经被glTF 2.0标准官方支持,通过扩展名KHR_draco_mesh_compression嵌入模型文件中。Three.js从很早的版本开始就内置了对应的解码器,这意味着兼容性不需要额外担心。需要注意的一点是,Draco解码必须在Web Worker中进行,解码本身需要消耗一定时间,所以它换来的是传输体积的下降,而不是解析速度的提升。对于首屏展示来说,这个交换几乎总是划算的。
压缩操作推荐使用官方提供的命令行工具draco_encoder,安装后执行一行命令即可:
# 对glTF模型执行压缩,压缩级别越高体积越小但耗时越长 draco_encoder -i model.gltf -o model.drc -cl 10
实际工程中更常见的做法是直接使用gltf-pipeline工具链,它可以一条命令完成glTF转换与Draco嵌入,输出的仍然是标准glTF/GLB文件:
npm install -g gltf-pipeline gltf-pipeline -i model.gltf -o model.glb --draco.compressionLevel 7
压缩等级取值范围是0到10,级别7是性价比比较高的选择,体积缩减明显且压缩耗时可控。级别开到10虽然体积最小,但压缩阶段可能需要数十秒甚至几分钟,只适合构建流水线中离线执行,不适合设计师本机频繁操作。
二、Three.js中正确加载Draco压缩模型
压缩后的模型不能直接用普通的GLTFLoader加载,需要先配置解码器路径。Three.js把Draco解码器做成了独立的wasm文件,通过CDN引入即可,不必打包进主bundle。配置代码如下:
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
const dracoLoader = new DRACOLoader();
// 指定解码器路径,使用CDN上的wasm文件
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
dracoLoader.setDecoderConfig({ type: 'js' }); // 兼容不支持wasm的环境时可切换为js
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
}, (progress) => {
// 利用加载进度做进度条展示,缓解用户等待焦虑
console.log(`已加载 ${(progress.loaded / progress.total * 100).toFixed(1)}%`);
}, (error) => {
console.error('模型加载失败', error);
});
这里有几个容易踩坑的点。第一,解码器路径版本要与Three.js版本匹配,跨大版本混用可能出现解码失败。第二,务必在模型加载完成后手动释放解码器资源,dracoLoader.dispose()可以避免页面切换后内存泄漏。第三,加载回调中的onProgress事件在HTTP响应头缺少Content-Length时拿不到总大小,CDN配置时记得开启该响应头,否则进度条会失效。
另外建议给加载过程加上降级策略。可以用navigator.connection或首屏测速判断用户网络状况,弱网用户直接给高清图轮播,正常网络才加载3D模型,这样既保住体验又保住性能。
三、纹理优化与LOD分级渲染
很多团队压缩完几何体后发现,文件还是很大,原因出在纹理上。一张4096乘4096的商品贴图可能就占了20MB以上,而手机屏幕上根本用不着这么高的分辨率。纹理优化的基本原则是:贴图长边控制在1024到2048像素之间,格式优先使用KTX2压缩纹理格式,它可以让GPU直接读取压缩后的纹理数据,显存占用能降到原来的四分之一左右。配合gltf-transform工具可以批量处理:
npx gltf-transform optimize model.glb model_opt.glb \ --texture-compress webp \ --simplify --simplify-ratio 0.5
LOD(Level of Detail)分级是另一个关键手段。思路是为主模型准备多个精度版本:用户进入页面先加载一个面数极低的预览版,比如只有5MB的简化网格,让用户立刻看到可交互的3D效果;主模型在后台继续加载,加载完成后无缝替换。Three.js可以借助THREE.LOD对象或自己控制加载时序来实现:
// 先加载低模,快速呈现可交互内容
loader.load('model_low.glb', (lowGltf) => {
scene.add(lowGltf.scene);
// 低模就位后立刻开始拉取高模
loader.load('model_high.glb', (highGltf) => {
scene.remove(lowGltf.scene);
lowGltf.scene.traverse(obj => {
if (obj.isMesh) obj.geometry.dispose(); // 释放低模资源
});
scene.add(highGltf.scene);
});
});
除了LOD,还可以结合视锥剔除、frustumCulled属性默认开启即可,以及按需渲染代替连续渲染——只有当用户拖拽旋转模型时才调用requestAnimationFrame渲染一帧,静止时不渲染,这在商品展示这种交互低频场景下能把GPU占用降到接近零。
四、服务端与网络层优化
模型文件体积再小,网络传输不合理也会拖慢加载。首先要确保GLB文件开启gzip或br压缩传输,GLB是二进制格式,服务端配置Content-Encoding: br后体积通常还能再降15%左右。Nginx配置示例:
location ~* \.(glb|gltf)$ {
brotli on;
brotli_comp_level 6;
expires 30d;
add_header Cache-Control "public, immutable";
}
其次是CDN和缓存策略。3D模型一旦上线,内容基本不变,应设置长达半年的强缓存配合内容哈希文件名,让回访用户直接命中本地缓存。再加上HTTP/2或HTTP/3协议,多个资源可以并行加载,避免队头阻塞。
最后建议在业务层做加载监控,通过PerformanceObserver采集模型的真实加载耗时并上报,按机型、网络环境分桶统计。有了数据才能判断优化效果,也方便发现特定低端机型上的异常瓶颈。综合来看,Draco压缩加纹理优化可以把模型从几十兆压到三五兆,再配合低模首屏和网络层优化,主流机型上3D模型的可用时间完全可以控制在两秒以内,达到与高清图片相当的加载体验。