从建模到Web端:我是如何搞定数字孪生项目3D资产的
最近接手了几个数字孪生项目,从工厂到园区再到智慧城市,踩了不少坑,尤其是3D模型资产这块——动辄几百MB的单个模型、GB级别的场景文件,直接往Web端丢根本没法用。今天就聊聊我梳理出的一套完整处理流程,全是实战中磨出来的经验。

先说说数字孪生3D资产的“脾气”
和普通Web3D项目比,数字孪生的模型真的很“特殊”:
首先是来源杂,既有CAD画的精密零件、BIM导出的建筑模型,还有GIS的地形数据,甚至还有美术手工建的设备模型,格式和精度乱七八糟;
其次是体积大,印象最深的一个工厂设备模型,原始文件200多MB,整个厂区汇总下来直接飙到GB级;
再者精度要求还不一样,远处的建筑糊一点没关系,近景的设备得看清螺丝细节,能交互的模型还得保证几何结构没错;
最后还得注意保密,工厂布局、设备参数都是商业机密,绝对不能随便丢第三方平台处理。

一套走通的资产处理流程
踩了无数次加载慢、模型变形的坑后,我整理出了7步处理管线,亲测好用:

第一步:给模型“分个级”
先按模型离相机的距离给所有资产分层,这是后续一切优化的基础:

• 近景(距离<50米):归为LOD0,得保细节,面数最多给到50万/个;
• 中景(50-200米):LOD1,中等精度就行,面数控制在10万/个;
• 远景(>200米):LOD2,能看清轮廓就够,面数压到1万/个。
第二步-第三步:建模导出,统一格式
这步主要靠美术同事配合,不管原始文件是什么格式,最后都统一导出成FBX——兼容性最好,后续处理不容易出问题。

第四步-第五步:格式转换+压缩,核心环节
这两步我全程用Zipoly处理,最关键的是它能完全离线操作,不用担心数据泄露,太适合保密项目了。
先把FBX转成GLB格式,支持批量处理文件夹,省了不少事;接着用Draco压缩(试过Meshopt,还是Draco兼容性更适配数字孪生场景),不同层级用不同压缩级别:

• LOD0(近景):压缩级别5-6,要是压太狠,设备的细节就没了;
• LOD1(中景):级别7,平衡压缩率和画质,不糊也不占空间;
• LOD2(远景):直接拉到8-9,反正远看也看不出来,极致压缩就对了。
第六步:贴图“瘦身”
贴图也是体积大头,按层级砍:

• LOD0保留2K分辨率,质量降到80(肉眼基本看不出差别);
• LOD1直接降到1K;
• LOD2要么512分辨率,要么干脆用纯色,省空间还不影响观感。
第七步:质检,别等上线才翻车
每次处理完,我都会用Zipoly自带的3D查看器挨个检查:看看模型有没有变形、穿模,贴图是不是对齐了,尤其是LOD0的近景细节,必须盯紧,不然上线后用户凑近看全是问题。

Web端实现LOD切换,让加载飞起来
处理完模型,还得在前端做LOD切换,不然加载还是慢。我用Three.js写了个LOD控制器,核心思路就是根据相机和模型的距离,自动切换不同精度的模型:

class LODController {
  constructor(scene, camera) {
    this.scene = scene
    this.camera = camera
    this.models = [] // 存模型位置、各层级模型、当前层级等信息
  }

  update() {
    const camPos = this.camera.position
    this.models.forEach(item => {
      // 计算相机和模型的距离
      const dist = camPos.distanceTo(item.position)

      // 判断该显示哪个层级
      let targetLOD
      if (dist < 50) targetLOD = 'lod0'
      else if (dist < 200) targetLOD = 'lod1'
      else targetLOD = 'lod2'

      // 层级变了就切换
      if (targetLOD !== item.current) {
        this.switchLOD(item, targetLOD)
      }
    })
  }

  async switchLOD(item, lod) {
    // 先卸载当前显示的模型
    if (item.loaded) {
      this.scene.remove(item.loaded)
    }
    // 加载新层级的模型
    const model = await this.load(item[lod])
    this.scene.add(model)
    item.loaded = model
    item.current = lod
  }
}

// 最后在渲染循环里调用update就行
function animate() {
  requestAnimationFrame(animate)
  lodController.update()
  renderer.render(scene, camera)
}

加载Draco压缩的GLB模型也有小技巧,一定要把解码器文件放在内网服务器,别依赖外网,不然加载容易出问题:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'

const loader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
// 解码器路径指向内网
dracoLoader.setDecoderPath('/static/draco/')
loader.setDRACOLoader(dracoLoader)

async function loadModel(url) {
  return new Promise((resolve, reject) => {
    loader.load(url, gltf => resolve(gltf.scene), null, reject)
  })
}

看看实际效果
上个月处理的一个工厂项目,142个模型文件,处理前总体积3.8GB,平均每个文件26.8MB,首屏直接卡得加载不出来;处理后总体积降到280MB,平均2.0MB——LOD0平均4.5MB,LOD11.2MB,LOD2才0.3MB,首屏加载时间直接压到5.8秒,用户体验直接起飞。

最后聊聊用到的工具
• Zipoly:核心工具,格式转换、Draco压缩、3D预览全搞定,离线操作是关键;
• Blender:偶尔用来修复杂材质、手动减面;
• Three.js:Web端渲染的核心引擎;
• gltf-pipeline:如果项目有CI/CD流水线,用它做自动化压缩很方便。
其实数字孪生的3D资产处理,核心就是“分级优化”——近景保细节,远景省空间,再配合前端的LOD切换,既保证体验又不卡加载。如果有同行也做这类项目,欢迎交流踩坑经验,毕竟这类项目细节太多,多聊聊能少走不少弯路。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐