接手过数字孪生项目的朋友,大概率被客户提过这么一句话:模型能不能点一下换个颜色?第一次听到这个需求,我以为就是一键高亮的变体,后来真正做进去才发现,点击高亮和切换模型颜色,看起来是一回事,底层逻辑和实现路径完全不在一个量级。
我是在一个智慧园区数字孪生项目的需求评审会上遇到的。当时客户指着三维园区里的楼栋模型说:“我们领导想看到不同入驻状态的楼,一眼就能区分出来,比如入驻率高的是绿色,低的是红色,鼠标点一下就能看到变化。”同事的第一反应是“这不就是高亮吗”,但高亮只能代表“你正在选中它”,代表不了“它的入驻率是高是低”。真正动手做的时候,牵涉到材质控制、状态管理、事件绑定、数据联动一整套东西。
这篇就把我在三维场景里做模型颜色切换的完整思路、操作路径和踩坑过程整理出来。核心环境是山海鲸可视化这类数字孪生平台,但原理通用于所有三维引擎(Three.js、Unity、UE等)。无论你是刚接触数字孪生的开发,还是正在做三维可视化交付的工程师,这篇文章应该都能帮上忙。
1. 高亮不是终点:三维交互里“选中态”的真实需求
1.1 两个真实需求:一个要“状态”,一个要“标记”
数字孪生项目里,“点击模型”要解决的需求,绝大多数不是“看清楚我点了谁”,而是“告诉系统,这个对象现在处于什么状态”。
比如智慧园区项目里,客户要的不是“鼠标点一下那栋楼,楼亮起来”,而是“这栋楼的招商状态是多少、空置率是否超标、能耗是否异常”。他们希望点击楼栋后,楼栋的视觉呈现马上切换到对应的业务状态——绿色代表健康、红色代表异常。这种需求,单靠高亮是表达不完整的。
再比如水利泵站的项目,巡检人员每天要在三维场景里核查几十台水泵。以前的方式是点一下泵,高亮显示,然后在右侧面板里看数据。客户后来提出,希望把“已巡检”“待巡检”“故障”三种状态直接做成颜色,哪台泵什么状态,一眼看过去就知道。清点数量、标记位置、状态确认这三个动作,全部靠颜色切换来完成。
这两个项目的共同点是什么?用户不是要一个“瞬时反馈”,而是要一个“可持续观察的状态表达”。
1.2 高亮是“瞬时强调”,换色是“状态持久化”
要把问题说清楚,得先分清两个概念。
高亮(Highlight)是渲染层面的“瞬时强调”,它的生命周期跟鼠标事件绑定。鼠标移上去,模型变亮或描边,鼠标移开,模型恢复原状。它的核心作用是告诉用户“你的指针当前命中了哪个对象”,是一种交互指引,不改变对象本身的业务属性。
切换颜色则不同,它通常意味着对象的状态属性发生了变化。比如一台设备的运行状态从“正常”变“报警”,模型颜色从绿色变成红色。这个变化是持久存在的,哪怕鼠标移开、相机旋转到别处再转回来,红色依然在,因为业务状态没有变。
我整理了一个对比表,方便理解:
| 维度 | 点击高亮 | 切换模型颜色 |
|---|---|---|
| 本质 | 渲染层的临时强调 | 业务状态的视觉映射 |
| 生命周期 | 随鼠标事件消失 | 随状态变更持久存在 |
| 是否影响数据 | 不影响 | 通常需要同步更新业务数据 |
| 实现复杂度 | 低,改渲染状态即可 | 高,涉及材质、状态机、数据联动 |
| 用户感知 | “我正在看这个” | “这个对象当前是什么状态” |
从表格能看明白,高亮解决的是“操作焦点”问题,换色解决的是“业务状态可视化”问题。数字孪生场景里,成熟的三维交互设计往往两者都要:鼠标悬停时高亮,告诉用户“你点的是哪个”;点击后换色,告诉用户“这个对象当前处于什么状态”。
1.3 二维界面里的“选中高亮”经验,为什么不能直接搬进三维
做过Web前端的人对高亮都不陌生:表格里hover行高亮、选中行高亮,Excel里高亮显示当前行列,GIS图层里框选高亮。但这些二维场景里的高亮,逻辑上跟三维数字孪生有很大不同。
二维表格的选中,核心是“当前正在操作哪条记录”。用户在高亮行上做编辑、删除、查看明细,高亮只是操作上下文。三维场景里,用户面对的不是一张数据表,而是一个物理空间的数字化映射。他关心的是“这台水泵现在是否在运行”“这栋楼的能耗是否超标”“这条管线的阀门是否关闭”。这些状态需要被“持续地看见”,而不是只在鼠标悬停时闪烁一下。
所以做三维数字孪生交互设计时,我一般会先问业务方一个问题:这个颜色变化,是仅仅为了让用户知道“我选中了它”,还是为了表达“它的状态变了”?如果是后者,就要按“状态持久化”的架构来设计,而不是简单改个高亮参数。
1.4 状态持久化带来的连锁需求
一旦确定要“状态持久化”,后面会牵出一串连锁需求:
- 状态需要被记忆:用户刷新页面、重新打开场景后,模型颜色还是不是上次的状态?
- 状态可以被修改:谁有权限修改状态?是点击模型的人,还是系统自动判断?
- 状态需要被传达:颜色变了之后,其他看板、图表、报表要不要同步更新?
- 状态有生命周期:设备从绿变红之后,会不会再变回绿色?谁来触发变回?
这些需求如果不在设计阶段想清楚,很容易出现“颜色是变了,但只是样子变了,数据没变”的半吊子交付。我在一个项目里就踩过这个坑,下面细说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 颜色到底是怎么来的:材质与光照如何决定一个模型长什么样
2.1 三维模型的“皮”:几何体、材质与贴图
要把模型颜色讲清楚,先得说清楚一个三维模型在引擎里由什么构成。
一个模型通常包含三部分:几何体(Geometry/Mesh)决定形状,材质(Material)决定表面属性,贴图(Texture)决定表面细节。几何体是“骨架”,材质是“皮肤”,贴图是皮肤上的“花纹”。
模型的颜色不是几何体自带的东西,而是材质和贴图共同作用的结果。可以这样理解:一个白色纸杯(几何体),刷上红色油漆(材质颜色),再贴上商标(贴图),最终呈现的是“红色的杯子+贴着商标”的视觉效果。材质决定了它的基础色彩、光泽度、透明度和反射特性,贴图则负责提供细节纹理。
在数字孪生场景里,绝大多数模型都是建筑、设备、管线、地形这类对象,它们往往带有丰富的贴图细节。比如厂房的墙面上可能贴有企业标识、楼栋玻璃幕墙有反射纹理、管线上有流向箭头。这些细节必须保留,否则模型会变得很“假”。
2.2 换色的三条路线:整体换材质、修改颜色属性、动态改贴图
基于上面的原理,在三维场景里给模型换颜色,业界有三条路线:
路线一:整体替换材质
把模型的材质直接换成另一个颜色的材质。比如模型原本用的是灰色材质,点击后换成蓝色材质。这种方式实现最简单,但问题也很明显——新材质如果没保留原贴图,模型会丢失表面细节;如果材质数量多,还容易导致GPU渲染状态频繁切换,影响性能。
路线二:修改材质颜色属性
保留原材质,只修改材质的颜色(color)属性。现在的三维引擎里,材质几乎都有颜色属性,它和贴图是“相乘”关系。也就是说,贴图是白色的,颜色属性决定最终色;贴图有彩色图案,颜色属性相当于加了一个“滤镜”。这种方式改动量小、性能开销低,而且贴图细节不会丢,是数字孪生项目里最常用的路线。
路线三:动态生成贴图
通过代码实时修改贴图像素,或者用Canvas等图形工具绘制新贴图再替换。这个方案最灵活,能实现非常精细的变色效果(比如管线里水流流动的变色动画),但开销最大,而且需要额外的图像处理能力,一般只在特殊效果需求下使用。
三条路线对比如下:
| 路线 | 实现成本 | 细节保留 | 性能开销 | 适用场景 |
|---|---|---|---|---|
| 整体替换材质 | 低 | 差 | 中 | 简单色块模型、快速原型 |
| 修改颜色属性 | 低 | 好 | 低 | 大多数数字孪生场景 |
| 动态生成贴图 | 高 | 最好 | 高 | 水流动画、特殊效果 |
2.3 数字孪生场景里,为什么“修改材质颜色属性”最合适
在数字孪生项目里,我几乎没有用过“整体替换材质”这个方案,原因就是细节丢失问题。
举个例子,一栋楼栋模型可能包含了墙面贴图、玻璃反射、楼顶广告牌等元素。如果直接整体换成红色材质,贴图全没了,模型看起来就像一块红色塑料,客户看到的第一反应就是“太假了”。而用修改材质颜色属性的方式,相当于在原本的模型表面“罩了一层红色玻璃”,贴图纹理依然可见,模型依然立体真实。
性能方面,修改颜色属性在底层实现上是修改一个GPU uniform变量,GPU渲染一帧只需要几微秒就能完成。就算场景里同时有几百个模型需要变色,也不会造成明显的帧率波动。更重要的是,这种方式不需要重新加载资源、不需要重新构建模型,操作是“瞬时”的,非常适合数字孪生里高频的交互操作。
顺便提一句,三维引擎里的光照也会影响最终呈现颜色。同一个材质颜色,在强光下看起来偏白,在暗光下看起来偏黑。所以做状态色设计时,要考虑到场景灯光环境。这个坑我在第5节细讲。
3. 山海鲸里的换色实操:事件配置与脚本控制两种路线
3.1 路线A:通过事件配置,在交互面板里给模型加“变色动作”
山海鲸可视化这类数字孪生开发平台,最大的优势是把底层三维引擎封装成了可视化的交互操作。对于大多数业务人员或者不愿写代码的交付工程师来说,路线A是最直接的选择。
操作逻辑大致是:在三维场景编辑界面里,选中一个模型对象,然后到交互事件配置面板里添加“点击”事件,事件的动作选择“设置模型颜色”,再指定目标颜色值即可。整个过程不需要写任何代码,纯配置完成。
我建议动手之前先做一件事:把颜色状态整理成一张配置表。比如:
| 状态 | 色值 | 业务含义 |
|---|---|---|
| normal | #4CAF50 | 正常 |
| warning | #FF9800 | 预警 |
| fault | #F44336 | 故障 |
| stopped | #9E9E9E | 停用 |
这张表的意义在于:先在业务逻辑层面达成一致,再去配置界面操作。否则全凭感觉选色,交付后客户说“红色不够明显”“绿色太刺眼”,来回返工是常有的事。
事件配置的路径上还需要注意一个细节:判断是只改“当前点击的这个模型”,还是“跟它同组的模型一起变”。比如点击一台水泵的进水管模型,希望出水管也同步变色,需要在配置时建立模型分组或联动关系。山海鲸里可以通过模型对象的父子关系或分组管理来实现,建议在建模阶段就把需要联动的模型放进同一分组,避免后期在配置面板里一个一个找。
3.2 路线B:脚本控制,给模型加一个状态开关
如果需求不满足“点一下转变色”,而是“点一下颜色变了,同时还要弹窗、还要写库、还要联动其他看板”,那就得走脚本路线。
山海鲸提供了脚本扩展能力,可以在事件触发时执行自定义逻辑。这类三维场景的脚本核心思路是通用的,可以理解为这样一个骨架:
javascript复制// 1. 获取场景中的目标模型对象
const model = scene.getObjectByName('pump_station_01');
// 2. 判断当前状态
if (model.userData.state === 'normal') {
// 3. 修改材质颜色
model.material.color.set('#F44336');
model.userData.state = 'fault';
// 4. 同步业务数据(示意)
updateDeviceStatus('pump_station_01', 'fault');
} else {
model.material.color.set('#4CAF50');
model.userData.state = 'normal';
updateDeviceStatus('pump_station_01', 'normal');
}
上面只是伪代码,不同平台的具体API不一样,但表达的是一个通用逻辑:先取模型对象,再改材质颜色,再更新状态标记,最后把状态同步到业务数据层。
有几个关键点需要特别说明:
第一,状态标记要存在模型对象上。 代码里的userData就是模型对象上挂载的“口袋”,专门用来存业务数据。状态存在这里,后续任何代码都能读到,不需要额外维护一个全局变量。
第二,颜色切换和业务数据要绑定。 如果只改了颜色、没有同步数据,刷新页面后颜色就会丢失。我在项目里见过太多“颜色能变,但关了页面再打开就还原了”的问题,根本原因就是颜色和数据没有打通。
第三,点击反馈要快。 脚本如果要做异步请求(例如把状态提交到后端接口),先立即改颜色给用户反馈,再在异步回调里做后续处理。不要让用户点了模型等半秒钟才看到颜色变化,这个等待体验非常糟糕。
3.3 两条路线的适用边界怎么判断
很多朋友问,到底是配置路线好还是脚本路线好。我的判断标准很简单:看这个颜色切换是“静态交互”还是“动态联动”。
静态交互:点击模型→换颜色,整个链路是固定死的,没有额外的数据计算、没有外部系统参与。这种情况用配置路线效率极高,交付速度快,客户自己也能维护。
动态联动:点击模型→换颜色→同时触发弹窗、更新大屏图表、向后端提交数据、联动其他模型。这种情况必须走脚本路线,因为配置面板无法覆盖这么复杂的逻辑。
两条路线不是互斥的,实际项目里我经常搭配使用:基础的状态颜色切换用配置实现,复杂的数据联动用脚本实现。比如在一个泵站项目里,点击水泵的“换色”操作靠配置完成,但换色的同时要往数据库写一条巡检记录、把工单状态从待处理变成处理中,这就是脚本的活。
多个模型,多个状态怎么管理?
项目做到后面,场景里的模型往往是几十上百个,状态也不止“正常/故障”两种。这时候需要一个统一的状态管理器,把“点击哪个模型、目标颜色、状态值”这三元组统一管理起来。我不建议把逻辑散落在各个模型的点击事件里,否则后期维护就是灾难。
4. 换色的真正价值:把颜色变成业务状态的“可视化编码”
4.1 先跟业务方定一张状态编码表
我做过十几个数字孪生项目之后,慢慢形成一个习惯:动手配置之前,先把业务方拉过来,面对面定一张“状态编码表”。
这张表要定清楚几件事:业务系统里到底有哪些状态值?每个状态值的颜色是什么?颜色切换的触发条件是什么?谁有权限触发?
以某工厂数字孪生项目为例,我们定的设备状态表长这样:
| 状态值 | 颜色 | 触发条件 | 权限 |
|---|---|---|---|
| 运行中 | 绿色 | 设备启动、定期巡检正常 | 系统自动 |
| 待机 | 蓝色 | 设备暂停、无生产指令 | 系统自动 |
| 报警 | 红色 | 参数超限、传感器触发 | 系统自动 |
| 检修中 | 黄色 | 工单创建、维护人员标记 | 维护人员手动 |
| 停用 | 灰色 | 设备报废、退出生产 | 管理员手动 |
这张表不是我拍脑袋定的,而是跟车间主任、设备维护员、生产调度一起商量出来的。为什么要这么较真?因为颜色承载的信息如果跟业务语义不一致,看板上的模型颜色就只是“好看的装饰”,不能成为管理决策的依据。
4.2 点击换色之后,数据如何跟着流转
颜色切换一旦跟数据打通,就不再是单纯的视觉展示,而是一个完整的数据流转链路。
以设备检修场景为例,完整链路是这样的:
- 维护人员在三维场景里点击一台设备模型
- 设备颜色从绿色变成黄色(表示进入检修状态)
- 脚本触发,向业务系统提交一条检修工单
- 工单系统生成工单编号,返回给三维场景
- 设备模型上挂载一个标签,显示“检修中 #20241015-001”
- 同一时刻,大屏上的“设备故障率”图表刷新,数据源多了这条记录
- 检修完成后,维护人员再次点击设备,选择“恢复运行”
- 设备颜色从黄色变回绿色,工单状态更新为已完成
这套链路跑通之后,三维场景就不再是“看着好看的数字沙盘”,而是变成了一个真实业务的操作入口。模型上的每一次颜色变化,都是数据库里的一次状态变更;数据库里每一次状态变更,都会在模型上产生颜色反馈。这种双向联动,才是数字孪生“虚实映射”的真正含义。
4.3 不同行业里,模型换色有哪些现成的玩法
把颜色切换做成行业解决方案,每个行业都有自己的玩法,我整理几个有代表性的:
智慧工厂: 产线设备按“运行/待机/故障/检修”四色显示;AGV小车按“执行任务/空闲/充电/异常”显示;物料库位按“满/半满/空/锁定”显示。颜色基本可以实时反映全厂设备的运转情况,管理人员不需要打开任何报表,扫一眼三维场景就能掌握全局。
智慧园区: 楼栋按“入驻率”分色,从深绿到深红渐变;公共区域按“人流量”分色;照明设施按“正常/故障/节能”状态分色。园区运营方对“哪栋楼该招商了、哪片区域设备该检修了”一目了然。
水利工程: 水泵机组按“运行/备用/检修/故障”分色;阀门按“开/关/半开”分色;水质传感器按“正常/预警/超标”分色;闸门按“提升中/下降中/到位/卡滞”分色。水利工程设备分散、巡检困难,颜色状态可视化能大幅降低巡检成本。
智慧交通: 信号灯路口的行人等待区按“安全/预警/危险”变色;隧道内照明灯按“正常/故障”变色;桥梁结构健康监测点按“安全/预警/超限”以热点颜色呈现。
这些场景的共性是:颜色本身就是信息载体,而且是全场景统一编码的“可视化语言”。一旦形成规范,整个团队看到某个颜色,不需要额外沟通就能同步理解业务状态。
5. 三维场景模型换色的避坑清单:从点击穿透到视觉还原
5.1 点击穿透与事件误触发
三维场景里做模型点击换色,第一个容易踩的坑是“点击穿透”问题。
什么情况会出现?如果你的场景里,目标模型外面罩着一层透明的碰撞体或装饰层(比如一个玻璃防护罩、一条无形的热力层),射线检测时优先命中的是外层对象,里层模型的点击事件就永远触发不了。或者反过来,用户在模型上拖动鼠标旋转视角时,触发了click事件,模型颜色“莫名其妙”变了。
解决思路有几个:
方案一:透明对象穿透。 射线检测时,如果命中的对象是透明材质,跳过它继续检测下一层。这在大多数三维引擎里都能配置。透明罩体本来就是视觉辅助,不需要承担点击交互。
方案二:事件阈值判断。 给click事件加一个位移阈值,鼠标按下到抬起之间移动距离超过一定像素(比如5像素),就判断为拖拽而非点击。这个方法在Three.js、Unity里都有现成实现,能有效避免旋转视角时误触发变色的情况。
方案三:按名称过滤。 代码里对射线检测结果做过滤,只允许特定名称前缀的模型触发点击变色。比如所有设备模型统一命名为“device_xxx”,检测到点击对象名称不以“device_”开头,直接忽略。这个方案在复杂场景里最暴力有效。
5.2 光照对颜色的“欺骗”与修正
颜色切换完成后,最容易让人崩溃的是什么?明明设置的色值是标准红#FF0000,场景里看起来却像暗红色,甚至偏紫。这不是设备显示问题,而是光照在“捣乱”。
三维引擎里,模型最终呈现的颜色 = 材质颜色 × 光照颜色 × 环境光。如果场景里用了暖色光(比如日落时分的模拟灯光),白色模型看起来就会偏黄;如果用了冷色光,白色就偏蓝。再加上阴影,模型不同朝向的同一面颜色亮度也不一致。
在做模型状态色之前,一定要做这几件准备工作:
统一场景光照基准。 数字孪生场景建议用白色平行光作为主光源,再配合中性色的环境光。不要用颜色过于饱和的光源,否则所有状态色都会受光源影响偏离设计值。
状态色预留视觉冗余。 如果业务方要求“报警必须非常明显”,色值不要直接用纯红,可以用更亮的品红或者增加材质的发光强度(emissive),让模型在光照不足时也能保持高辨识度。自发光部分不受光照影响,是突出状态颜色的利器。
在最终目标设备上做颜色验收。 客户现场的显示器往往和我们开发用的显示器有色彩差异。交付前务必在现场大屏上实测几个状态的色值,确认明暗对比、饱和度都符合预期后再验收。
5.3 不要只靠颜色:状态显示的“多通道”原则
还有一个容易忽略的问题:色觉异常人群。
正常人群中约有8%的男性和0.5%的女性存在不同程度的色觉异常(俗称色盲)。如果业务看板完全依赖“红绿”来区分正常和故障,这部分用户基本无法使用。更稳妥的做法是“多通道”传递信息——不只用颜色,还叠加图标、文字、闪烁动画、尺寸变化等元素。
实际项目里,我通常会这样设计一个故障状态模型:
- 模型颜色变为红色(颜色通道)
- 模型外围出现一个快速闪烁的红色光圈(动画通道)
- 模型上方悬浮一个“故障”标签,带工单号(文字通道)
- 选中的设备在场景右侧面板里显示详细信息(数据通道)
这样即使颜色认不出来,其他通道也能把信息准确传达。《信息技术 隧道运维管理数字孪生系统技术要求》这类规范里,对状态显示的可读性也有相关要求,原理都是相通的:信息传递不能“单通道”。
5.4 频繁换色会不会拖垮性能
最后聊一个经常被问到的问题:场景里几百个模型频繁换颜色,性能扛得住吗?
直接给结论:只改材质颜色属性,不新建材质、不动态加载贴图,几百个模型的颜色切换对帧率的影响几乎可以忽略。
真正会卡的是什么情况?是每次换色都新建一个材质对象。有些不太熟练的开发者,会在点击事件里写“new THREE.MeshBasicMaterial({color: '#F44336'})”,然后赋值给模型。这样每次点击都创建一个新材质、旧材质又没释放,时间一长内存就爆了,GPU的渲染状态切换也会造成掉帧。
正确做法是先定义好材质池,把需要的几种状态材质预先创建出来,点击时直接引用已有的材质对象,或者只改材质color属性的值,不去碰材质对象本身。
换句话说:颜色是状态属性的值,不是对象本身。别把“改颜色”做成“建新材质”,这是三维场景性能优化里最常见也最基础的一条原则。
最后分享一个交付现场的体会
有一次给客户演示设备状态变色功能,我提前把设备A设置成故障状态,模型显示为红色。结果演示开始后,客户指着模型说:“不对,这台设备今天上午刚检修完,已经恢复运行了。”我当时有点尴尬,但反过来也验证了这个功能的价值——三维场景里的颜色不仅仅是展示,它已经成为客户心中“业务事实”的载体。
从那以后,我养成了一个习惯:交付这类需求时,一定先把状态编码表和业务方确认清楚,再谈技术实现。技术上的材质切换、脚本联动都是可以复用的能力,但业务状态的定义一旦有歧义,模型上的颜色就失去了意义。
希望这篇文章能给正在做或者准备做数字孪生三维交互的朋友一些参考。如果你也在模型状态显示上踩过坑,欢迎交流讨论,互相补补课。
