如何用配置驱动一套 Three.js 数字孪生模板

在数字孪生和 3D 可视化项目里,经常会遇到一种很熟悉的情况:行业变了、模型变了、页面名称变了,但很多基础功能并没有变。

无论项目是智慧病房、办公楼宇、智慧园区还是生产车间,通常都需要处理相似的问题:初始化三维场景、切换楼层或区域、展示资产点位、区分设备状态、点击查看详情,以及把相机恢复到合适的观察位置。

如果每接到一个新场景都重新搭建这些能力,项目很容易陷入重复开发。于是我做了一次尝试:把稳定的三维交互保留在模板中,把行业差异放进配置文件,让同一套代码可以生成多种数字孪生演示。

本文记录这个模板目前的设计和实现。它不是完整的数字孪生平台,也没有假装解决所有行业问题。现阶段更准确的定位,是一个可以继续接入模型和业务数据的 Web3D 演示基础。

先明确目标:复用交互,不是统一业务

不同数字孪生项目之间确实存在共性,但也有明显差异。

共性通常包括:

  • 三维场景和相机控制
  • 楼层或区域切换
  • 资产点位与状态颜色
  • 资产详情面板
  • 场景标题、主题和初始视角
  • 模型加载、尺寸调整和坐标校准

差异则更多来自行业业务。例如,病房关心床位终端、监护设备和环境传感器;园区关心楼宇、门禁、停车和安防;工厂关心机械臂、输送线、能耗和安全生产。

因此,我没有试图设计一套覆盖所有行业的固定业务模型,而是把模板拆成三个部分:

  1. 通用渲染与交互层:负责场景、相机、楼层、点位、状态和详情交互。
  2. 场景配置层:描述项目名称、主题、空间布局、楼层、资产和初始相机。
  3. 行业数据层:通过资产类型和指标字段表达病房、楼宇、园区或工厂的业务差异。

这种划分的核心不是"所有项目都一样",而是先找出值得稳定复用的部分,再给差异留下足够空间。

当前项目结构

模板目前使用 Three.js、TypeScript 和 Vite,主要目录如下:

text 复制代码
configs/                    # 每个场景一份 JSON
├── smart-ward.json
├── office-building.json
├── campus-park.json
└── factory-floor.json
scripts/
└── generate.mjs            # 按配置生成独立构建目录
src/
├── config/scene-config.ts  # 配置加载与校验
├── domain/types.ts         # 场景、楼层和资产类型
├── domain/ward.ts          # 楼层与资产查询
├── scene/WardScene.ts      # Three.js 场景和点位交互
├── ui/AppShell.ts          # 通用页面结构
├── ui/AppController.ts     # 配置驱动的界面状态
└── main.ts                 # 读取配置并启动应用

虽然最初是从智慧病房场景开始的,但现在项目已经包含 wardofficecampusfactorygeneric 五种程序化布局。

一份场景配置包含什么

下面是一份经过精简的配置。字段来自项目当前实际使用的结构:

json 复制代码
{
  "project": {
    "title": "智造工场",
    "subtitle": "生产车间数字孪生可视化演示",
    "theme": "industrial"
  },
  "scene": {
    "layout": "factory",
    "scale": 1,
    "initialCamera": [15, 12, 16]
  },
  "floors": [
    {
      "id": "assembly-line",
      "name": "一层装配线",
      "level": 1,
      "assets": [
        {
          "id": "factory-robot-01",
          "name": "1号装配机械臂",
          "type": "robot",
          "category": "生产设备",
          "status": "online",
          "position": [-4.6, 2.2, 1.7],
          "metrics": {
            "运行状态": "自动运行",
            "今日产量": "1,286件",
            "节拍": "42秒"
          }
        }
      ]
    }
  ]
}

这份配置大致分成三组。

1. 项目信息

project 控制标题、副标题和视觉主题。目前主题支持:

  • medical:医疗空间
  • office:办公和园区空间
  • industrial:工厂和工业空间

主题不负责表达具体业务,它主要控制颜色、界面气质和状态展示方式。

2. 三维场景

scene.layout 决定使用哪一种程序化布局;initialCamera 决定进入页面时的相机位置;scale 用于模型或场景整体缩放。

配置还预留了可选的 scene.model 字段:

json 复制代码
{
  "scene": {
    "layout": "generic",
    "model": "/models/customer-building.glb",
    "scale": 0.8,
    "initialCamera": [13, 10, 14]
  }
}

演示阶段可以先使用程序化几何快速验证交互;进入正式项目后,再替换为经过授权和优化的 GLB/GLTF 模型。

3. 楼层和资产

floors 不一定只能表示物理楼层,也可以表示园区分区、车间区域或其他空间层级。每个区域包含一组 assets

资产的 type 可以自由扩展,例如:

text 复制代码
camera
sensor
access-control
robot
conveyor
energy-meter
medical-monitor

模板只要求资产具备统一的基础信息,具体行业指标放在 metrics 中。这使详情面板可以保持通用,而不必为每种设备建立一套固定字段。

配置不能只"能读",还要尽早报错

配置化降低了改代码的频率,但也会带来新的问题:错误可能从 TypeScript 编译阶段移动到 JSON 运行阶段。

例如,以下问题都很常见:

  • 相机或点位坐标不是三个数字
  • 状态值拼写错误
  • 楼层 ID 重复
  • 不同楼层中的资产 ID 重复
  • 同时使用新字段 assets 和旧字段 devices
  • 必填标题或指标为空

因此模板没有直接把 JSON 强制断言成 TypeScript 类型,而是通过 parseSceneConfig() 做运行时校验。下面是其中一段简化后的逻辑:

ts 复制代码
const statuses = ['online', 'warning', 'offline'] as const;
const layouts = ['ward', 'office', 'campus', 'factory', 'generic'] as const;
const themes = ['medical', 'office', 'industrial'] as const;

function requireVector(value: unknown, field: string): [number, number, number] {
  if (
    !Array.isArray(value) ||
    value.length !== 3 ||
    value.some((item) => typeof item !== 'number' || !Number.isFinite(item))
  ) {
    throw new Error(`${field} 必须是三个数字`);
  }

  return value as [number, number, number];
}

解析完楼层后,还会检查 ID 唯一性:

ts 复制代码
const floorIds = new Set<string>();
const assetIds = new Set<string>();

floors.forEach((floor) => {
  if (floorIds.has(floor.id)) {
    throw new Error('楼层 id 必须唯一');
  }
  floorIds.add(floor.id);

  floor.assets.forEach((asset) => {
    if (assetIds.has(asset.id)) {
      throw new Error('资产 id 必须唯一');
    }
    assetIds.add(asset.id);
  });
});

这些检查看起来不复杂,却能避免很多难以定位的交互问题。例如资产 ID 重复时,详情面板可能选中错误对象;坐标中出现字符串时,Three.js 场景则可能出现异常位置或计算结果。

配置错误越早被发现,后续制作演示、打包和交付就越稳定。

四类场景如何共用一套模板

目前模板准备了病房、办公楼宇、园区和工厂四份配置。

智慧病房

病房配置使用 medical 主题和 ward 布局,资产包括床位终端、监护设备、输液设备、门禁和环境传感器。楼层切换可以对应不同护理区或监护区。

这里需要特别说明:截图中的患者、设备状态和指标全部是虚构演示数据,不应用于医疗运营或诊疗决策。

办公楼宇

楼宇场景关注楼层、门禁、摄像头、环境传感器和能源设备。与病房相比,通用的楼层切换和资产详情没有变化,变化的是主题、布局、资产类型和指标。

智慧园区

园区不一定有传统意义上的上下楼层,因此可以把 floors 理解为分区或管理范围。停车、安防、环境监测和建筑设备都可以用统一资产结构组织。

生产车间

工厂使用 industrial 主题和 factory 布局。机械臂、输送线、能耗表、摄像头与环境传感器仍然使用同一个点位和详情机制,但指标会变成节拍、产量、能耗和维护状态。

从代码角度看,四个页面并不是四套独立应用。它们共享场景管理、相机、点位、楼层切换和详情面板,只是在启动时加载不同配置。

用生成命令输出独立版本

仅仅能在开发环境中切换配置还不够。实际演示和交付往往需要把每个场景构建成独立目录。

项目提供了一个生成脚本,可以指定配置文件和输出目录:

bash 复制代码
npm run generate -- \
  --config configs/factory-floor.json \
  --out dist/factory-floor

脚本会完成几件事:

  1. 确认配置文件存在并且扩展名是 .json
  2. 解析 JSON,检查 projectscenefloors 等基础字段。
  3. 将配置名称写入 VITE_SCENE_CONFIG
  4. 调用 Vite 构建指定输出目录。

其他场景使用相同方式生成:

bash 复制代码
npm run generate -- --config configs/smart-ward.json --out dist/smart-ward
npm run generate -- --config configs/office-building.json --out dist/office-building
npm run generate -- --config configs/campus-park.json --out dist/campus-park

生成后的静态目录可以分别预览、部署或压缩交付。这样做也让场景配置和构建结果之间保持清晰对应。

新增一个行业场景,需要做哪些工作

配置驱动并不意味着"换几个名称就自动完成一个项目"。更准确地说,它把工作从重复编写基础功能,转移到场景建模、数据整理和坐标校准上。

增加一个新场景时,我会按以下顺序处理:

第一步:确定空间表达方式

先判断现有程序化布局是否够用。如果只是概念验证,可以从 generic 或接近的布局开始;如果要展示真实建筑和设备,就需要准备 GLB/GLTF 模型。

第二步:建立场景配置

创建新的 JSON 文件,确定标题、主题、布局、初始相机、楼层或区域,以及第一批资产。

第三步:校准模型和坐标

真实模型接入后,需要检查:

  • 模型原点是否合理
  • 单位和整体比例是否正确
  • 朝向是否与场景坐标一致
  • 是否需要按楼层或区域拆分
  • 资产点位是否能准确落在模型上

这一部分无法被通用配置完全替代,也是数字孪生项目里最容易低估的工作。

第四步:补充行业指标

typecategorymetrics 表达行业差异。如果某类交互确实具备复用价值,再将它提升到通用能力层,而不是一开始就把所有可能性写进模板。

第五步:生成和验收

运行生成命令后,逐项检查初始视角、楼层切换、资产点击、状态颜色、详情字段、响应式布局和模型性能。

接入真实模型之后,性能问题才真正开始

当前内置布局主要使用程序化几何,适合快速演示。正式项目换成建筑、园区或设备模型后,文件体积、网格数量、贴图尺寸和材质数量都会直接影响网页体验。

常见的处理包括:

  • 删除不可见结构和无效节点
  • 对高面数模型做减面
  • 合并可合并的网格与材质
  • 压缩贴图并控制分辨率
  • 根据距离设置 LOD
  • 将大型园区按区域或楼层拆分
  • 按需加载,而不是一次加载全部内容
  • 在目标设备上测试显存和帧率

因此,"支持 GLB/GLTF"只代表模板提供了接入入口,并不意味着任意模型放进去都能直接获得良好性能。建模规范、资产优化和加载策略仍然是完整交付的一部分。

当前边界

这个模板目前已经能完成多场景演示,但我会明确区分"已经具备"和"后续可以接入"。

当前没有包含:

  • 后端服务
  • 登录和权限系统
  • 实时物联网数据
  • 告警闭环和工单流程
  • 面向生产环境的性能指标承诺

演示数据全部来自本地虚构配置。正式项目要接入资产台账、设备状态或 GIS 数据,还需要设计接口、状态同步、异常处理、权限和数据安全策略。

把这些边界写清楚并不会削弱模板价值。相反,它能让演示层、数据层和业务系统之间的职责更加明确,也能避免在项目初期作出无法验证的承诺。

后续:不把技术路线限制在 Three.js

Three.js 很适合当前的 Web3D 模板,但数字孪生和三维建模并不只有一种技术选择。

后续我计划围绕更完整的实践链路继续更新,包括:

  • 使用 Blender 处理模型、原点、层级、材质和轻量化
  • 使用 Cesium 处理 GIS、地形和大范围城市空间
  • 对比 Three.js 与 Babylon.js 在项目结构和工具链上的差异
  • 探索 Unity、Unreal Engine 在高质量实时渲染中的适用场景
  • 研究 GLB/GLTF、LOD、贴图压缩和流式加载
  • 将物联网数据、资产台账和可视化大屏接入三维场景

我的目标不是证明某个框架可以解决所有问题,而是持续记录:面对不同规模、行业和交付要求时,怎样选择合适的三维技术,并把项目做成真正能够演示、维护和扩展的产品。

总结

这次模板改造带来的最大变化,不是少写了几个页面,而是建立了更清晰的项目边界:

  • 通用能力留在代码中
  • 场景差异进入配置
  • 行业指标跟随资产组织
  • 真实模型单独完成优化和坐标校准
  • 独立场景通过生成命令输出

配置驱动不能消除数字孪生项目中的建模、数据和业务工作,但它可以减少基础交互的重复实现,让更多时间花在真正决定项目质量的部分。

这个项目还会持续更新。后续我会继续记录三维建模、模型优化、GIS、实时数据接入,以及不同渲染技术在数字孪生项目中的实际取舍。


推荐标签: Three.js、数字孪生、3D可视化、Web3D、前端开发、GLTF、可视化

相关推荐
用户83134859306984 天前
Vue+Threejs实现人物奔跑场景
vue.js·webgl·three.js
元气少女小圆丶5 天前
unity发布web嵌入到前端页面的接受参数
前端·unity·webgl
WebGIS开发7 天前
WebGIS开发进阶教程①:WebGL坐标系
gis·webgl·gis开发
虚焦像素_8 天前
MapLibre GL 实战:一套引擎搞定 2D 平面与 3D 球面
webgl
山河木马10 天前
GPU自动处理专题4-填充覆盖区域(光栅化)
javascript·webgl·计算机图形学
智商偏低10 天前
WebGL 百万线段高性能 Demo(批量绘制,无循环绘制)
webgl
weixin_4368040712 天前
毒蘑菇性能测试增强版 - 毒蘑菇性能测试 - 在线 WebGL GPU 帧率压力测试工具
webgl
山河木马13 天前
GPU自动处理专题3-NDC怎么映射成屏幕像素坐标(视口变换)
javascript·webgl·图形学
山河木马13 天前
GPU自动处理专题2-NDC是什么(透视除法)
javascript·webgl·计算机图形学