在数字孪生和 3D 可视化项目里,经常会遇到一种很熟悉的情况:行业变了、模型变了、页面名称变了,但很多基础功能并没有变。
无论项目是智慧病房、办公楼宇、智慧园区还是生产车间,通常都需要处理相似的问题:初始化三维场景、切换楼层或区域、展示资产点位、区分设备状态、点击查看详情,以及把相机恢复到合适的观察位置。
如果每接到一个新场景都重新搭建这些能力,项目很容易陷入重复开发。于是我做了一次尝试:把稳定的三维交互保留在模板中,把行业差异放进配置文件,让同一套代码可以生成多种数字孪生演示。
本文记录这个模板目前的设计和实现。它不是完整的数字孪生平台,也没有假装解决所有行业问题。现阶段更准确的定位,是一个可以继续接入模型和业务数据的 Web3D 演示基础。
先明确目标:复用交互,不是统一业务
不同数字孪生项目之间确实存在共性,但也有明显差异。
共性通常包括:
- 三维场景和相机控制
- 楼层或区域切换
- 资产点位与状态颜色
- 资产详情面板
- 场景标题、主题和初始视角
- 模型加载、尺寸调整和坐标校准
差异则更多来自行业业务。例如,病房关心床位终端、监护设备和环境传感器;园区关心楼宇、门禁、停车和安防;工厂关心机械臂、输送线、能耗和安全生产。
因此,我没有试图设计一套覆盖所有行业的固定业务模型,而是把模板拆成三个部分:
- 通用渲染与交互层:负责场景、相机、楼层、点位、状态和详情交互。
- 场景配置层:描述项目名称、主题、空间布局、楼层、资产和初始相机。
- 行业数据层:通过资产类型和指标字段表达病房、楼宇、园区或工厂的业务差异。
这种划分的核心不是"所有项目都一样",而是先找出值得稳定复用的部分,再给差异留下足够空间。
当前项目结构
模板目前使用 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 # 读取配置并启动应用
虽然最初是从智慧病房场景开始的,但现在项目已经包含 ward、office、campus、factory 和 generic 五种程序化布局。
一份场景配置包含什么
下面是一份经过精简的配置。字段来自项目当前实际使用的结构:
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
脚本会完成几件事:
- 确认配置文件存在并且扩展名是
.json。 - 解析 JSON,检查
project、scene和floors等基础字段。 - 将配置名称写入
VITE_SCENE_CONFIG。 - 调用 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 文件,确定标题、主题、布局、初始相机、楼层或区域,以及第一批资产。
第三步:校准模型和坐标
真实模型接入后,需要检查:
- 模型原点是否合理
- 单位和整体比例是否正确
- 朝向是否与场景坐标一致
- 是否需要按楼层或区域拆分
- 资产点位是否能准确落在模型上
这一部分无法被通用配置完全替代,也是数字孪生项目里最容易低估的工作。
第四步:补充行业指标
用 type、category 和 metrics 表达行业差异。如果某类交互确实具备复用价值,再将它提升到通用能力层,而不是一开始就把所有可能性写进模板。
第五步:生成和验收
运行生成命令后,逐项检查初始视角、楼层切换、资产点击、状态颜色、详情字段、响应式布局和模型性能。
接入真实模型之后,性能问题才真正开始
当前内置布局主要使用程序化几何,适合快速演示。正式项目换成建筑、园区或设备模型后,文件体积、网格数量、贴图尺寸和材质数量都会直接影响网页体验。
常见的处理包括:
- 删除不可见结构和无效节点
- 对高面数模型做减面
- 合并可合并的网格与材质
- 压缩贴图并控制分辨率
- 根据距离设置 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、可视化