用 Codex 完成智慧高速 3D 可视化大屏,从 18 张行业设计稿到可交互的运行监测中心
英文标题:用 Codex 完成智慧高速 3D 可视化大屏,从 18 张行业设计稿到可交互的运行监测中心
因为Codex 最近重置得太频繁了,我们索性就趁这个机会,一口气为智慧交通领域生成了一大批可视化大屏设计稿。结果可想而知——生成出来的 18 张设计稿里,有不少其实是千篇一律的。 🍋遇到的问题 快速批
117.5 KB
98.5 KB
89.1 KB
119.4 KB
114.5 KB
99.0 KB
112.5 KB
96.6 KB
77.4 KB
91.1 KB
101.2 KB
88.3 KB
93.4 KB
100.4 KB
95.2 KB
124.9 KB
120.4 KB
103.1 KB
中文
因为Codex 最近重置得太频繁了,我们索性就趁这个机会,一口气为智慧交通领域生成了一大批可视化大屏设计稿。结果可想而知——生成出来的 18 张设计稿里,有不少其实是千篇一律的。
🍋遇到的问题
快速批量生成的代价就是同质化。UI 框架差不多,布局逻辑也差不多,信息怎么放来放去就那几种套路。这显然不行。
所以我们开始反思,怎样才能真正沉淀出一套好用的设计系统。几个方向正在推进:
- 素材库和组件化 :把图标、头部容器、各种框线都积累成可复用的组件库。字体也要选好,视觉层级要分明
- 图表库的创建和打磨 :柱状图、环形图、时间线……这些基础图表模板得反复校对,确保数据展示的逻辑一致
- 数据素材的积累 :每次生成都留下一些真实感强的数据例子,后面的设计稿就能参考这些数值范围和分布特征
幸好 Codex 重置得够频繁 ,这样的快速迭代成本其实不高。我们可以放心地尝试不同的设计方向,通过不断修正和积累,慢慢搭建出属于这个领域的设计系统。
🍋我们回到正题
我们先展示 18 张行业设计稿,每个交通领域各三张,看看都有哪些设计思路。然后从里面挑出最有潜力的智慧高速方案,实际动手开发成了一个可以交互的前端项目。
接下来就逐个拆解技术细节:三维道路怎么构建、Shader 怎样绘制流光、车辆怎样沿曲线运动、点位怎样联动相机,还有昼夜切换是怎么做的。每个部分都会给出关键代码和实现思路,同时标注哪些是设计选择、哪些是技术实现、哪些是后续接入真实数据时需要改的地方。
这次实践从 Codex 重置前的一轮设计生成开始。先围绕交通行业制作大屏设计稿,再选择其中一张,继续完成网页开发、三维场景和交互。本文展示六类行业的 18 张设计稿,并以最终实现的「智慧高速运行监测中心」说明从图片到代码的过程。
项目采用 React、TypeScript 和 Three.js。完成的页面支持车辆流动、单车信息查看、节点视角切换、车辆跟随和昼夜切换;业务面板使用演示快照,车辆沿程序生成的轨迹运动。
🍋01|先看行业设计稿:六类交通场景,18 张方案
以下 18 张为设计探索图,每类三张;其中图表数值仅用于版式示意,不作为行业统计数据。每组按运行总览、专项协同或设施管理展开。这轮设计覆盖高速、铁路、公路、水运、民航与邮政交通。随后从高速方向选定一张设计稿,开发可交互的前端项目。
🍓01 / 智慧高速
高速设计分别覆盖互通运行、服务区和桥隧保障。多层道路关系适合在三维中表达;通行量、速度和事件处理状态仍需要表格或图表提供明确口径。
设计探索|智慧高速运行中心|以山区多层互通为视觉中心,搭配节点流量与运行指标,强调路网空间关系。
设计探索|智慧高速服务区协同|聚焦停车、充电和服务设施,呈现服务区内部资源分布,适合讨论高峰保障与服务调度。
设计探索|智慧高速桥隧保障|沿桥梁、隧道和边坡组成的走廊展开,把不同设施的保障信息集中呈现。
🍓02 / 智慧铁路
铁路设计分别面向运输组织、站城衔接和线路运维。三维站场可以辅助理解空间,但运行图、联锁和正式调度指令仍属于各自业务系统。
设计探索|智慧铁路运输指挥|以高铁枢纽为核心,展示不同方向的运输关系,探索运输指挥场景的整体构图。
设计探索|智慧铁路站城协同|用站房剖面串起候车、站台、到达和接驳区域,突出旅客在不同空间之间的流动。
设计探索|智慧铁路线路运维|把轨道、道岔、供电与检测对象联系起来,服务线路巡检和设施运维的设计表达。
🍓03 / 智慧公路
普通公路设计将山区干线、城乡衔接和桥梁养护分开。前两者关注网络与节点,后者关注结构与病害位置,避免在同一画面混用网络统计和构件指标。
设计探索|智慧公路运行总览|以山区国省干线为主体,按路段组织通行信息,让路网运行状况更容易被定位。
设计探索|智慧公路城乡协同|把城镇主街、县道与乡道衔接起来,探索城乡道路协同和节点通行的展示方式。
设计探索|智慧公路养护管理|通过桥梁剖面和检测信息呈现养护对象,使设施状态与养护任务有清晰的空间对应。
🍓04 / 智慧水运
水运设计按航道、港区和船闸拆分。航道条件随水情变化,船闸需要队列与闸次计划;三维空间展示需要与这些时序和调度数据结合。
设计探索|智慧水运航道运行|沿上、中、下游分段组织航道与船舶信息,突出水路通行的连续关系。
设计探索|智慧水运港航协同|把泊位、货轮、堆场和集疏运道路放进同一场景,探索港口作业与外部运输的协同。
设计探索|智慧水运船闸调度|以双线船闸为核心,呈现水位、闸室及上下游待闸区域,适合讨论船舶过闸调度。
🍓05 / 智慧民航
民航设计分别组织机场运行、旅客流程和货运保障。场面空间、旅客节点和货运作业属于不同对象,不能仅以更换模型复用同一套指标。
设计探索|智慧民航运行中心|用跑道、滑行道和机坪建立机场全景,围绕航班与地面运行安排信息。
设计探索|智慧民航旅客服务|沿值机、安检、候机等旅客流程组织画面,关注不同环节的客流和服务压力。
设计探索|智慧民航货运保障|围绕货机装卸、进出口和中转作业展开,呈现航空货运保障的空间与流程关系。
🍓06 / 智慧邮政交通
邮政交通设计分别覆盖寄递网络、分拣场地和末端配送。网络层关注流向与积压,场地层关注设备和动线,末端层关注服务覆盖与履约异常。
设计探索|智慧邮政寄递调度|以分拨枢纽连接区域运输网络,呈现寄递链路和车辆调度的整体关系。
设计探索|智慧邮政智能分拣|展示分拣线、输送设备和格口,关注处理状态、异常位置与设备维护。
设计探索|智慧邮政末端配送|连接网点、驿站、智能柜和社区,探索末端配送覆盖与服务资源的组织方式。
🍓从设计主题核对业务模型:六类行业的差异在哪里
交通运输部对公路水路基础设施数字化转型的解读,将数字化管养、运行监测预警、应急指挥调度和跨部门协同列为建设内容。由此可以把大屏设计拆为三个层次:对象在哪里、状态是否异常、异常由谁处理。只有前两个层次的可视化,仍不等于完成了业务处置。
交通运输部·综合规划司|《关于支持引导公路水路交通运输基础设施数字化转型升级的通知》解读(2024-05-01)(www.mot.gov.cn/gongkai/zcj…
下表依据公开资料归纳管理任务;数据项及 2D/3D 的取舍属于面向本案例的工程分析,不是主管部门规定的统一界面。铁路的运行图、水运的过闸队列、民航的保障时间线和邮政的流向网络,都有各自的数据结构与阅读方式。
| 领域 | 管理对象与核心问题 | 需要组织的数据 | 三维与二维的分工 |
|---|---|---|---|
| 高速 | 主线、匝道、互通和服务区;判断影响方向及处置资源 | 断面流量、速度、事件、气象、设备与救援资源 | 3D 解释立交层次与局部现场;2D 比较全网拥堵、趋势和待办 |
| 铁路 | 区间、站场、桥隧、轨道和供电等设施;风险关联 | 设备告警、检测、检修、环境风险和获授权的行车信息 | 3D 定位设施与现场;2D 保留线路拓扑、运行图和风险台账 |
| 普通公路 | 国省干线、路面、桥梁、边坡;资产评估与养护安排 | 病害、巡查、检测、工单、施工占道和通阻状态 | 3D 复核构件与地形;2D 管理线路分布、工单和年度计划 |
| 水运 | 航道、船闸、桥区、港区与船舶;通航条件及调度衔接 | 水位、净空、航道尺度、船舶动态、闸次和待闸队列 | 3D 表达船闸与港区关系;2D 展示电子航道图、队列和水位曲线 |
| 民航 | 航班、机位、场道、车辆和保障任务;空地协同 | 航班状态、资源占用、保障节点、超时和气象 | 3D 理解场面空间与回放;2D 展示航班时间线、里程碑与异常清单 |
| 邮政交通 | 分拨场地、干线运输、网点和末端;积压传播与场地安全 | 处理节点、到离场、积压、设备状态和安全隐患 | 3D 定位场内设备与动线;2D 分析网络流向、时效和节点积压 |
公路资料明确了设施检查、养护和检测等管理对象;铁路资料强调风险分级与隐患治理;水运资料列出了航道监测对象和联合调度方向;民航试点涉及空地联合态势与保障协同;邮政案例覆盖分拨场地巡查及隐患闭环。这些资料支持的是业务范围,不能直接证明某套大屏已具备相应能力。
国务院(交通运输部公开)|公路安全保护条例(2011-03-07)(xxgk.mot.gov.cn/jigou/fgs/2…
国家铁路局|国家铁路局关于印发《铁路安全风险分级管控和隐患排查治理管理办法》的通知(2023-03-10)(www.nra.gov.cn/xxgk/gkml/z…
交通运输部·水运局|交通运输部关于加快智慧港口和智慧航道建设的意见(2023-12-04)(xxgk.mot.gov.cn/jigou/syj/2…
中国民用航空局|关于在成都双流机场开展智慧民航建设全流程运行场景试点工作的通知(2024-03-04)(www.caac.gov.cn/XXGK/XXGK/T…
河北省邮政管理局·雄安新区邮政管理局|雄安局强化远程监控巡查(2026-08-14)(he.spb.gov.cn/hbsyzglj/c2…
🍋02|选择智慧高速:从一张设计稿确定开发对象
本次选定的开发基准是「智慧高速运行监测中心」。画面以多层互通为中心,周围配置节点流量、事件处置、车型结构、设备在线率和趋势对比。这张方案同时包含空间关系、统计信息与对象操作,可将设计到代码的过程拆为清晰的实现任务。
选定开发设计稿|智慧高速运行监测中心。中央互通、五个点位及四周业务面板构成后续布局和交互的基准。
预览视频:
中央多层道路需要重建曲线、桥面高度与车道边界;行驶车辆需要模型资产、连续姿态和可查询的对象标识;五个点位需要连接相机目标与屏幕标签;周边图表则需要统一统计口径。由此形成“道路怎样构建—车辆怎样运动—点击后怎样关联信息与视角”的开发主线。
实现范围:当前项目是可交互的前端演示。业务面板使用固定快照,车辆采用程序生成的轨迹;道路由选定设计稿重建,未接入测绘路网、实时交通数据或实际调度指令系统。文中编号 V0869、P001 等均为演示对象。
开发结果|智慧高速运行监测中心白天总览。中央为程序生成的三维路网与车辆,四周为快照驱动的流量、事件、车型和设备面板。
🍓把选定画面对应到高速运行管理任务
交通运输部公布的江苏“AI+智慧路网”案例,将营运单位、交通执法、高速交警纳入协同指挥,并汇聚气象、基础设施和应急资源等数据。对应到系统设计,发现事件、研判影响、签发指令、现场执行和确认恢复,需要不同的状态与权限。地图上出现一个告警点,不代表调度流程已经完成。
交通运输部(案例提供方:江苏省交通运输厅)|交通运输部发布12项交通强国建设试点典型案例——案例2“AI+智慧路网”智护高速公路平安通畅(2026-08-26)(www.mot.gov.cn/xinwen/jiao…
| 指标 | 必须明确的口径 | 本项目的实现 |
|---|---|---|
| 今日通行 | 车次或独立车辆;统计范围、断面与去重方式 | 快照中节点流量求和为 48,000;与场景车辆数独立 |
| 平均速度 | 道路范围、时间窗口、来源与聚合方法 | 读取固定快照的 88 km/h;不由画面车辆反推 |
| 设备在线率 | 设备清单、心跳有效期、维护停机和无数据状态 | 固定数据合计 990/1,000,得到 99.0% |
| 处理中事件 | 事件去重与状态机;开始、结束和恢复条件 | 按快照状态筛选出 3 起;未接入真实派单流程 |
四个指标的时间尺度也不同。车辆位置属于轨迹观测,断面流量属于窗口统计,设备在线依赖心跳,养护状态可能来自工单。生产接口需要同时携带观测时间、接收时间、来源与质量标记。浏览器动画流畅,只说明渲染连续,不能证明数据实时。
🍋03|用 Codex 推进开发:从设计图拆到工程边界
工程拆分采用“业务需求—设计基准—资产拆分—场景实现—交互验证”的路径。Codex 承担设计生成、代码编写和检查等执行工作;每一步的可用结果仍需要由对应产物证明,例如布局配置、模型文件、可操作页面与测试记录。
| 步骤 | 落地产物 | 需要验证的问题 |
|---|---|---|
| 行业与场景拆解 | 对象、指标、事件、使用者及数据边界 | 指标是否回答真实业务问题;是否混用不同统计口径 |
| 选择设计基准 | 整屏选定设计稿与固定画布 | 标题、侧栏、场景和趋势区域的比例是否确定 |
| 拆分视觉资产 | 背景、标题、边框、图标、点位资源 | 一个边框由哪个容器绘制;内部图表可用区域有多大 |
| 建立三维场景 | 道路曲线、路面、车辆、地形和相机 | 空间是否连续;道路、标线、车辆是否采用同一车道基准 |
| 连接交互 | 点位定位、单车选择、跟随、昼夜与暂停 | 选择的对象、信息面板和相机目标是否一致 |
| 验证与交付 | 代码测试、运行截图、检查记录 | 功能、视觉一致性与目标设备性能是否分别验收 |
页面采用 1672 × 941 的设计画布;中央场景参照区域为 x=395、y=178、宽 881、高 462。最外层按浏览器宽高的较小比例统一缩放,使标题、面板和场景共享缩放关系。标题背景、面板底图与内容层各自承担明确职责,避免重复叠框。
代码 1|整屏等比缩放
const scale = Math.min(size.w / 1672, size.h / 941);const stageStyle = { left: (size.w - 1672 * scale) / 2, top: (size.h - 941 * scale) / 2, transform: `scale(${scale})`,};
这段代码按原实现抽取了 style 表达式,便于说明缩放关系。它保持整屏比例,不会自动改成移动端卡片布局;当目标屏幕宽高比不同时,会出现居中留白。图表内部仍需分配绘图区、坐标轴、图例和数值区,不能仅把一个小图表塞进大边框。
当前流量条形图、车型环图和趋势图由 React/SVG 实现,使用各自的 viewBox 与数据映射;事件和设备区域使用表格。虽然依赖清单含 ECharts,不能据此把这些业务图表写成 ECharts 实现。
🍋04|渲染架构:业务状态与逐帧计算分开
| 模块 | 职责 |
|---|---|
| main.tsx/default-dashboard.json | 注册业务块、导入布局、设置整屏缩放 |
| state.tsx | 保存节点、车辆、跟随、昼夜、暂停和流光开关等 UI 状态 |
| ExpresswayScene.tsx | 创建/销毁场景,将 React 状态传给 Three.js 运行时 |
| sceneLayout.ts/createExpressway.ts | 选定设计稿坐标重建、道路与环境几何、渲染循环和资源释放 |
| traffic.ts/vehicleModel.ts | 加载 GLB 与贴图、计算车辆姿态、更新实例矩阵和处理拾取 |
| sceneEffects.ts/cameraRig.ts | 道路与点位效果、目标相机、车辆跟随和标签投影 |
React 管理业务选择和页面状态;Three.js 在 requestAnimationFrame 中更新相机、车辆和材质 uniform。普通运行时,车辆信息约每 500 ms 回传到 React,避免把每帧的所有实例矩阵写入组件状态。这个频率是界面信息刷新策略,不是后端数据采样频率。
渲染循环使用受上限约束的 dt 推进 elapsed,暂停时冻结 elapsed,车辆、道路流光和水面共享这个动画时钟。相机过渡与昼夜切换仍可使用 dt 完成操作反馈。页面隐藏后停止循环,卸载时取消动画、移除监听器并释放几何、材质、纹理和渲染器。
🍋05|道路重建:屏幕坐标、曲线与车道共用一个基准
🍓选定设计稿逆投影,解决主视角构图对齐
设计图只提供二维像素位置。项目先固定透视相机,再把选定设计稿上的道路控制点转成标准化设备坐标(NDC),发出射线,与指定高度的水平面求交。每个控制点包含 x、y 和人工设定的 height;由这些三维点生成 Catmull-Rom 曲线。
代码 2|由设计稿像素得到场景控制点
const ray = new THREE.Raycaster();ray.setFromCamera(new THREE.Vector2( (x - SCENE_RECT.x) / SCENE_RECT.width * 2 - 1, 1 - (y - SCENE_RECT.y) / SCENE_RECT.height * 2), camera);const point = ray.ray.intersectPlane( new THREE.Plane(new THREE.Vector3(0, 1, 0), -height), new THREE.Vector3());if (!point) throw new Error('Reference point is above the reconstruction plane');
这相当于为二维像素补充一个深度约束:平面方程 y − height = 0 决定射线落点。它能让总览视角中的道路走势接近选定设计稿,但单张图不足以恢复唯一的真实几何。相机切到侧面后,还需要检查桥面高差、交叉关系和连接连续性。接入真实路网时,应改用经过坐标与高程处理的道路数据。
API 依据|Three.js Raycaster:NDC 与相机射线(threejs.org/docs/pages/…
🍓路面、标线、车辆和流光保持同一横断面
道路采用沿曲线扫掠的带状网格。每个采样点取曲线位置 p 和切线 d,用水平侧向量 (−d.z, 0, d.x) 将道路展开到左右边界,再把相邻横断面连接为两个三角形。UV.x 对应横向位置,UV.y 对应沿线的归一化弧长位置。
代码 3|路面横断面与 UV 的生成
for (let i = 0; i <= segments; i++) { const t = i / segments; const p = curve.getPointAt(t), d = curve.getTangentAt(t); const side = new THREE.Vector3(-d.z, 0, d.x).normalize(); for (const k of [-1, 1]) { const q = p.clone().addScaledVector( side, offset + k * (typeof width === 'function' ? width(t) : width) / 2 ); verts.push(q.x, q.y + height, q.z); uv.push((k + 1) / 2, t); }}
carriageway() 统一定义中央分隔、道路边缘、车道宽度与各车道中心。车辆偏移、道路标线和 Shader 采样均使用该基准,可减少车辆压线、流光越过护栏等错位。当前侧向量按水平面计算,没有建立带超高的完整道路横断面;它适合本演示的构图重建,不等同于道路工程设计模型。
🍋06|道路 Shader:窄亮带与短光包怎样形成
道路效果包含两层:MeshStandardMaterial 保留沥青表面和基础光照;独立 ShaderMaterial 在道路上方绘制流光。几何高度分别为道路曲线之上约 0.03 和 0.06 个场景单位,减少共面闪烁。流光保留深度测试,关闭深度写入,并采用加法混合,因此不会替代道路本身的材质。
片元着色器首先把横向 UV 转成道路空间位置 across,再根据中央分隔和车道宽度求出车道内局部坐标 x。沿线距离 travel 由 UV.y × roadLength 得到,direction 区分两侧车流方向。这样,动画的周期按道路长度组织,而不是把所有道路强行压成同一张平面动画。
代码 4|沿车道移动的短光包
float across = (vUv.x - .5) * width;float lane = (abs(across) - median) / laneWidth;float x = (fract(lane) - .5) * laneWidth;float direction = across > 0.0 ? 1.0 : -1.0;float laneIndex = floor(lane);float seed = laneIndex * 23.71 + direction * 11.3;float travel = vUv.y * length * direction;float phase = mod(travel - time * speed + seed, 61.0 + laneIndex * 8.0);float tail = smoothstep(0.0, 6.0, phase) * (1.0 - smoothstep(6.1, 7.8, phase));float headDistance = (phase - 6.0) / .85;float head = exp(-headDistance * headDistance);float aa = max(fwidth(x), .035);float core = 1.0 - smoothstep(.055, .055 + aa, abs(x));
phase 控制光包沿道路循环;tail 用两段 smoothstep 形成短尾迹;head 用高斯形状集中亮度;core 把能量收束到车道中心的细线。fwidth 提供与屏幕像素变化有关的过渡宽度,用于减轻细线远距离观察时的锯齿。以上节选依赖完整着色器中的 varying、uniform 与最终颜色输出。
代码 5|限制发光范围并保留连续细带
float halo = exp(-x * x / .18);float mask = step(median, abs(across)) * (1.0 - step(edge, abs(across)));float ribbonDistance = abs(abs(across) - (median + laneWidth * .72));float ribbonCore = 1.0 - smoothstep( .045, .045 + max(fwidth(across), .04), ribbonDistance);float ribbonHalo = exp(-ribbonDistance * ribbonDistance / .16);float wave = .35 + .65 * pow(.5 + .5 * sin(travel * .035 - time * .8), 3.0);float packet = core * tail * .55 + halo * (tail * .25 + head * .45);float ribbon = (ribbonCore * .72 + ribbonHalo * .24) * wave;float alpha = (packet + ribbon) * mask * flowEnabled;
mask 把效果限制在有效路面内;packet 负责离散短光包;ribbon 负责两侧靠近快车道的连续细带;wave 调整连续带的明暗节奏。单独加粗光带会遮住标线,而把整条路染亮会削弱层次。因此,宽度、光晕和透明度需要分别控制。
项目还为部分行驶车辆绘制跟随车身矩阵的短尾迹。道路光包和车辆尾迹不是同一机制:前者由 time、speed 和道路 UV 决定,后者跟随具体车辆。当前道路流光没有根据实测流量、拥堵等级或单车速度调整,不能将它解释为真实交通流仿真。
🍋07|车辆实现:模型外观、运动轨迹与身份关联
🍓模型资产:按车型加载,按部件实例化
小客车、货车、客车分别加载 sedan.glb、truck.glb、bus.glb。资产转换包含尺寸和前向轴归一化、车轮轴心分离、材质调整及贴图处理。小客车来源为 Comrade1280 的 Generic passenger car pack,许可为 CC BY 4.0;货车与客车来源为 Rgsdev 的 Free Low Poly Vehicles Pack,许可为 CC0。项目保留作者、来源、许可和修改说明。
模型来源|Comrade1280:Generic passenger car pack(Sketchfab)(sketchfab.com/3d-models/g…
模型来源|Rgsdev:Free Low Poly Vehicles Pack(OpenGameArt)(opengameart.org/content/fre…
运行时遍历模型内部 Mesh,每个车型的每个部件建立一个 InstancedMesh。车身、车轮和灯具保留独立部件,因此车轮可以旋转,灯具也可以随夜景增强。实例化减少重复模型的绘制调用,但调用次数仍与部件及材质数量有关,不会把整个车队自动变成一次 draw call。
API 依据|Three.js InstancedMesh:实例矩阵与绘制调用(threejs.org/docs/pages/…
车轮转角按 θ = elapsed × speed ÷ radius 推进,即用行驶距离除以轮胎半径。该实现依赖资产转换后的轴向和尺寸约定。外置小客车纹理在当前 FBX→GLB 转换链中使用 flipY=true;这属于本项目资产的修正,不能直接套用到所有 glTF 纹理。
🍓弧长运动:速度不依赖控制点分布
代码 6|计算车辆的道路位置与朝向
if (vehicle.stationary) { return { t: .5, p: vehicle.stationary.position.clone(), rotation: vehicle.stationary.rotation.clone() };}const t = wrap(vehicle.phase + elapsed * vehicle.speed / vehicle.road.length * vehicle.direction);const p = vehicle.road.curve.getPointAt(t);const forward = vehicle.road.curve.getTangentAt(t);p.addScaledVector( new THREE.Vector3(-forward.z, 0, forward.x).normalize(), vehicle.offset);p.y += vehicle.kind === 'car' ? .03 : .015;forward.multiplyScalar(vehicle.direction);const right = new THREE.Vector3().crossVectors(new THREE.Vector3(0, 1, 0), forward).normalize();const up = new THREE.Vector3().crossVectors(forward, right).normalize();return { t, p, rotation: new THREE.Quaternion().setFromRotationMatrix( new THREE.Matrix4().makeBasis(right, up, forward)) };
elapsed × speed ÷ road.length 将行驶距离换算为归一化进度,wrap 把进度循环到 [0,1)。getPointAt() 按曲线弧长映射取点,可减轻控制点疏密造成的速度变化;getTangentAt() 提供方向,再与侧向和上方向构造车辆姿态。这里的“等速”仍受曲线弧长采样精度影响。
API 依据|Three.js Curve:getPointAt 与 getTangentAt(threejs.org/docs/pages/…
同一车道中的车辆采用相同速度,以保持既定间距;当前没有跟驰、超车、换道、拥堵传播和匝道合流模型。主线端点延伸到允许视角之外,匝道端部用透明度渐变减轻循环突变。车辆沿三维道路运动已实现,但不能据此声称完成交通微观仿真。
界面速度使用 speed × 3.6 展示为 km/h,含有场景单位按米解释的演示约定;由于道路不是测绘模型,这个速度不是实车测量值。distance 来自 elapsed × speed,也是合成行驶距离。接入生产轨迹后,位置、速度和里程应回到各自的数据来源与坐标基准。
运行截图|选中 V0869 后显示车型、道路与演示速度。信息来自当前车辆对象和同一套姿态计算,没有关联真实车牌或实测轨迹。
🍓拾取映射:instanceId 不等于稳定车辆 ID
每帧先把可见车辆写入按车型划分的 slots,再按 slot 更新实例矩阵。相机移动后,可见对象会变化,因此 instanceId 是当前渲染批次的序号,不能直接当作业务车辆 ID。拾取需要用“车型+实例序号”查回 slots 中的车辆。
代码 7|将射线命中的实例关联到车辆实体
const meshes = parts.map(p => p.mesh);meshes.forEach(m => m.computeBoundingSphere());const hit = raycaster.intersectObjects(meshes, false) .find(h => h.instanceId !== undefined);if (!hit || hit.instanceId === undefined) return null;const vehicle = slots[hit.object.userData.kind as VehicleKind][hit.instanceId];return vehicle ? { id: vehicle.id, distance: hit.distance } : null;
模型更新后重新计算包围球,避免动态实例使用过期边界。命中车辆后,外层还比较部分不透明场景网格的距离,过滤被这些网格遮挡的对象;这不是覆盖全部透明物、实例化环境和复杂场景的通用遮挡方案。最终返回稳定 ID,信息面板与跟随相机均按该 ID 查找车辆。
停车车辆使用独立 stationary 分支,位置和姿态不随 elapsed 改变,并输出 0 km/h、0 行驶距离。这样能让停车状态与主路运动明确区分,也便于用时间推进后的重复拾取验证对象身份。
运行截图|服务区 P001 驻车近景。停车对象同样使用 GLB 资产与实例拾取,stationary 分支保持姿态不变,信息面板显示停车状态。
🍋08|点位、相机与昼夜:同一对象跨视角保持一致
点位由三维底盘、光柱和投影到 DOM 的标签共同组成。底盘 Shader 以半径构造圆环,以角度和时间构造断续旋转弧,再叠加扩散脉冲;选中状态改变颜色和强度。标签则使用 Vector3.project(camera) 从世界坐标转为场景内像素坐标。
这种拆分让文字保持清晰,同时让标记底部跟随三维位置。当前底盘和光柱关闭深度测试,以保证跨桥面可读;它们是语义覆盖标记,不是物理灯光,也不能依据它们是否被遮挡判断真实现场可见性。
代码 8|进入跟车视角的过渡与持续跟随
progress = Math.min(1, progress + dt / 1.35);if (progress < 1) { const t = progress * progress * (3 - 2 * progress); camera.position.lerpVectors(from.position, desired, t); target.lerpVectors(from.target, look, t);} else if (instantTrack) { camera.position.copy(desired); target.copy(look);} else { camera.position.lerp(desired, 1 - Math.exp(-dt * 7)); target.copy(look);}camera.lookAt(target);camera.updateMatrixWorld();
进入跟车时使用约 1.35 秒的 smoothstep 过渡;稳定跟随后,位置以 1 − exp(−7dt) 为插值系数,降低帧间隔变化对平滑速度的影响。节点视角同样保存 position 和 target 两组端点,并支持在过渡中重新选择目标。上述代码是跟车分支节选,节点分支使用相同的缓动形式。
选择节点会清除车辆选择;选择车辆会退出固定点位视角。跟随对象发生路径循环时,运行时识别归一化进度的大幅跳变,结束本轮跟随并回到总览,避免相机跟随对象瞬间跨越路网。
运行截图|东枢纽视角。相机目标、点位标签投影与三维场景同步更新,周围业务面板保留原有布局。
昼夜切换由连续变量 night∈[0,1] 驱动,统一插值环境光、太阳光颜色与强度、背景、雾、环境贴图强度、路灯、玻璃发光和车辆灯光。夜景没有仅通过叠加黑色遮罩实现;曝光还略有提高,以保留路面和车辆轮廓。
代码 9|用同一个夜景参数协调光照
night = THREE.MathUtils.damp(night, nightTarget, 4, dt);effects.night.value = night;ambient.intensity = THREE.MathUtils.lerp(1.3, .45, night);sun.intensity = THREE.MathUtils.lerp(3.1, .45, night);renderer.toneMappingExposure = 1.1 + night * .12;scene.environmentIntensity = .035 * (1 - night * .7);lampMaterial.emissiveIntensity = .1 + night * 3.5;glass.emissiveIntensity = night * .6;serviceLights.forEach(light => { light.intensity = night * 260; });
节选省略了颜色、背景与雾的插值,以及接近目标时直接吸附的分支。车辆头灯使用实例化平面光束,路面灯池由材质计算,服务区另有灯光对象;它们并非为每辆车配置真实投射阴影的独立灯源。完整光照效果与运行开销,需要在目标 GPU 上共同评估。
运行截图|夜间总览。夜景参数同时作用于环境、路面、设施及车辆灯光;业务面板仍显示同一演示快照,不代表截图采集时刻的真实交通。
🍋09|从演示到运行系统:需要补齐哪些数据能力
视觉模型、连续运动、模型拾取与演示对象关联、真实业务数据是四项独立能力。当前项目实现了前三项;生产接入还需要轨迹来源、时空匹配、数据质量、事件状态及操作回执。以下是接口设计要求的拆解,不是本项目已经实现的后端功能。
| 层次 | 接口至少需要说明的信息 | 展示与处理策略 |
|---|---|---|
| 实体与空间 | entityId、实体类型、roadId、方向、laneId、坐标系及高程基准 | 统一业务 ID;完成坐标转换后再绑定模型与面板 |
| 时间与来源 | observedAt、receivedAt、source、质量状态、有效期限 | 展示数据截至时间;过期后保留最后已知状态或停止外推 |
| 轨迹 | 连续观测点、采样间隔、速度来源及定位质量 | 插值用于平滑,不把插值位置标为新观测;断流单独处理 |
| 统计 | 时间窗口、断面范围、聚合与去重方法 | 分别计算流量、速度和车辆数,不用场景实例数代替运营统计 |
| 事件 | eventId、等级、检测与确认时间、责任单位、状态、证据和回执 | 呈现检测、确认、派发、执行、恢复的状态迁移 |
| 操作 | 用户角色、授权范围、指令编号、确认与执行结果 | 将查看、建议和指令执行区分;失败与未回执状态可见 |
接入真实车辆信息时,公开大屏与业务终端还需要不同的信息粒度。展厅页面可使用模拟对象;业务页面按任务展示匿名目标 ID、类别、最后观测时间和数据来源。真实车辆轨迹涉及个人信息边界,实际处理依据、权限与保存要求需要按适用场景另行确定;前端显示一个编号并不等于完成合规处理。
🍋10|最后
从当前的智慧高速演示项目出发,我们需要把可视化大屏的整个生产链路继续深化。核心方向是:重交互、轻性能、动画流畅 的可视化智能平台建设。
🍋第一阶段:交互与布局基础的统一
我们已经完成了拖拽 npm 库的基础开发,现有大屏都建立在这个基础上。但暴露出来的问题很明显:
- 编辑模式与预览模式的布局资源不统一 :拖拽时的布局数据、预览时的渲染逻辑、导出时的坐标映射,三者之间存在割裂。需要统一底层布局模型,让所有模式共用一套坐标与尺寸基准
- 组件状态同步的复杂度 :每个组件在拖拽中被改动后,需要同步到预览、同步到数据结构、同步到导出格式,流程冗长且容易出错
解决思路 :建立统一的布局引擎。编辑、预览、导出都围绕这个引擎运转,减少模式间的数据转换成本。
🍋第二阶段:组件库与图表库的沉淀
智能可视化的核心就是自己的组件库 ,这是一个长期的过程。
开源组件库:
我做的平台组件库(正在构建)
现状 :
- 已经预留了一些扩展口子
- 接入了 ECharts 资源库与 Three.js 官网组件资源
- 自己做了一套组件库,但还不够理想
需要做的 :
- 图表组件库的深化
-
* 柱状图、折线图、散点图等基础图表要有统一的数据接口- 根据前面积累的设计稿中的数据样本,不断完善图表的视觉效果和交互反馈
- 建立图表配置的最小化 API,让平台能自动推荐最合适的图表类型
- 三维组件的集成
-
* 基于 Three.js 官方资源,包装出易用的 3D 场景容器、道路、车辆、流光等通用模块- 三维组件要支持拖拽调整视角、相机预设切换、对象拾取等交互
- 函数库的建立与反馈
-
* 为每个组件建立计算函数库(坐标映射、数据聚合、动画插值等)- 把这些函数喂给平台学习 ,让平台能理解"这个数据适合用什么图表、什么配置"
🍋第三阶段:可视化智能体平台
有了统一的组件库和函数库,下一步就是让平台具备智能生产能力 。
核心能力 :
- 自动推荐 :用户给出业务场景和数据源,平台自动推荐合适的组件组合与布局方案
- 智能配置 :根据函数库和历史设计稿的样式偏好,自动生成初稿配置,用户调整细节即可
- 批量生产 :同一个大屏模板,能快速适配不同的数据源、业务对象和地域,实现模板化生产
这一阶段相当于把设计知识和技术决策沉淀成平台的学习材料,降低每个新项目的从零开始的成本。
🍋最终目标:交互革新,用户畅享
所有这些工作的指向都是同一个初心:
用可视化与自动化技术的应用,提升用户体验,让用户在网页上畅享流畅、智能的交互体验。
具体体现在:
- 流畅 :拖拽无卡顿、动画帧数稳定、三维场景响应迅速
- 智能 :平台能理解业务需求,快速生成高质量方案,减少用户的重复劳动
- 可用 :组件库成熟,接口统一,用户无需深入学习就能上手构建大屏
这不只是技术堆积,而是围绕用户体验 和生产效率 的完整闭环。每一步都要验证实际效果。
说了很多接下来的构想,回到正题。我把上面的设计稿和智慧高速3D源码整理了一下.
如果你需要源码参考的话,可以来 柳杉前端 公众号 同名文章自取哈。
English / 原文
因为Codex 最近重置得太频繁了,我们索性就趁这个机会,一口气为智慧交通领域生成了一大批可视化大屏设计稿。结果可想而知——生成出来的 18 张设计稿里,有不少其实是千篇一律的。
🍋遇到的问题
快速批量生成的代价就是同质化。UI 框架差不多,布局逻辑也差不多,信息怎么放来放去就那几种套路。这显然不行。
所以我们开始反思,怎样才能真正沉淀出一套好用的设计系统。几个方向正在推进:
- 素材库和组件化 :把图标、头部容器、各种框线都积累成可复用的组件库。字体也要选好,视觉层级要分明
- 图表库的创建和打磨 :柱状图、环形图、时间线……这些基础图表模板得反复校对,确保数据展示的逻辑一致
- 数据素材的积累 :每次生成都留下一些真实感强的数据例子,后面的设计稿就能参考这些数值范围和分布特征
幸好 Codex 重置得够频繁 ,这样的快速迭代成本其实不高。我们可以放心地尝试不同的设计方向,通过不断修正和积累,慢慢搭建出属于这个领域的设计系统。
🍋我们回到正题
我们先展示 18 张行业设计稿,每个交通领域各三张,看看都有哪些设计思路。然后从里面挑出最有潜力的智慧高速方案,实际动手开发成了一个可以交互的前端项目。
接下来就逐个拆解技术细节:三维道路怎么构建、Shader 怎样绘制流光、车辆怎样沿曲线运动、点位怎样联动相机,还有昼夜切换是怎么做的。每个部分都会给出关键代码和实现思路,同时标注哪些是设计选择、哪些是技术实现、哪些是后续接入真实数据时需要改的地方。
这次实践从 Codex 重置前的一轮设计生成开始。先围绕交通行业制作大屏设计稿,再选择其中一张,继续完成网页开发、三维场景和交互。本文展示六类行业的 18 张设计稿,并以最终实现的「智慧高速运行监测中心」说明从图片到代码的过程。
项目采用 React、TypeScript 和 Three.js。完成的页面支持车辆流动、单车信息查看、节点视角切换、车辆跟随和昼夜切换;业务面板使用演示快照,车辆沿程序生成的轨迹运动。
🍋01|先看行业设计稿:六类交通场景,18 张方案
以下 18 张为设计探索图,每类三张;其中图表数值仅用于版式示意,不作为行业统计数据。每组按运行总览、专项协同或设施管理展开。这轮设计覆盖高速、铁路、公路、水运、民航与邮政交通。随后从高速方向选定一张设计稿,开发可交互的前端项目。
🍓01 / 智慧高速
高速设计分别覆盖互通运行、服务区和桥隧保障。多层道路关系适合在三维中表达;通行量、速度和事件处理状态仍需要表格或图表提供明确口径。
设计探索|智慧高速运行中心|以山区多层互通为视觉中心,搭配节点流量与运行指标,强调路网空间关系。
设计探索|智慧高速服务区协同|聚焦停车、充电和服务设施,呈现服务区内部资源分布,适合讨论高峰保障与服务调度。
设计探索|智慧高速桥隧保障|沿桥梁、隧道和边坡组成的走廊展开,把不同设施的保障信息集中呈现。
🍓02 / 智慧铁路
铁路设计分别面向运输组织、站城衔接和线路运维。三维站场可以辅助理解空间,但运行图、联锁和正式调度指令仍属于各自业务系统。
设计探索|智慧铁路运输指挥|以高铁枢纽为核心,展示不同方向的运输关系,探索运输指挥场景的整体构图。
设计探索|智慧铁路站城协同|用站房剖面串起候车、站台、到达和接驳区域,突出旅客在不同空间之间的流动。
设计探索|智慧铁路线路运维|把轨道、道岔、供电与检测对象联系起来,服务线路巡检和设施运维的设计表达。
🍓03 / 智慧公路
普通公路设计将山区干线、城乡衔接和桥梁养护分开。前两者关注网络与节点,后者关注结构与病害位置,避免在同一画面混用网络统计和构件指标。
设计探索|智慧公路运行总览|以山区国省干线为主体,按路段组织通行信息,让路网运行状况更容易被定位。
设计探索|智慧公路城乡协同|把城镇主街、县道与乡道衔接起来,探索城乡道路协同和节点通行的展示方式。
设计探索|智慧公路养护管理|通过桥梁剖面和检测信息呈现养护对象,使设施状态与养护任务有清晰的空间对应。
🍓04 / 智慧水运
水运设计按航道、港区和船闸拆分。航道条件随水情变化,船闸需要队列与闸次计划;三维空间展示需要与这些时序和调度数据结合。
设计探索|智慧水运航道运行|沿上、中、下游分段组织航道与船舶信息,突出水路通行的连续关系。
设计探索|智慧水运港航协同|把泊位、货轮、堆场和集疏运道路放进同一场景,探索港口作业与外部运输的协同。
设计探索|智慧水运船闸调度|以双线船闸为核心,呈现水位、闸室及上下游待闸区域,适合讨论船舶过闸调度。
🍓05 / 智慧民航
民航设计分别组织机场运行、旅客流程和货运保障。场面空间、旅客节点和货运作业属于不同对象,不能仅以更换模型复用同一套指标。
设计探索|智慧民航运行中心|用跑道、滑行道和机坪建立机场全景,围绕航班与地面运行安排信息。
设计探索|智慧民航旅客服务|沿值机、安检、候机等旅客流程组织画面,关注不同环节的客流和服务压力。
设计探索|智慧民航货运保障|围绕货机装卸、进出口和中转作业展开,呈现航空货运保障的空间与流程关系。
🍓06 / 智慧邮政交通
邮政交通设计分别覆盖寄递网络、分拣场地和末端配送。网络层关注流向与积压,场地层关注设备和动线,末端层关注服务覆盖与履约异常。
设计探索|智慧邮政寄递调度|以分拨枢纽连接区域运输网络,呈现寄递链路和车辆调度的整体关系。
设计探索|智慧邮政智能分拣|展示分拣线、输送设备和格口,关注处理状态、异常位置与设备维护。
设计探索|智慧邮政末端配送|连接网点、驿站、智能柜和社区,探索末端配送覆盖与服务资源的组织方式。
🍓从设计主题核对业务模型:六类行业的差异在哪里
交通运输部对公路水路基础设施数字化转型的解读,将数字化管养、运行监测预警、应急指挥调度和跨部门协同列为建设内容。由此可以把大屏设计拆为三个层次:对象在哪里、状态是否异常、异常由谁处理。只有前两个层次的可视化,仍不等于完成了业务处置。
交通运输部·综合规划司|《关于支持引导公路水路交通运输基础设施数字化转型升级的通知》解读(2024-05-01)(www.mot.gov.cn/gongkai/zcj…
下表依据公开资料归纳管理任务;数据项及 2D/3D 的取舍属于面向本案例的工程分析,不是主管部门规定的统一界面。铁路的运行图、水运的过闸队列、民航的保障时间线和邮政的流向网络,都有各自的数据结构与阅读方式。
| 领域 | 管理对象与核心问题 | 需要组织的数据 | 三维与二维的分工 |
|---|---|---|---|
| 高速 | 主线、匝道、互通和服务区;判断影响方向及处置资源 | 断面流量、速度、事件、气象、设备与救援资源 | 3D 解释立交层次与局部现场;2D 比较全网拥堵、趋势和待办 |
| 铁路 | 区间、站场、桥隧、轨道和供电等设施;风险关联 | 设备告警、检测、检修、环境风险和获授权的行车信息 | 3D 定位设施与现场;2D 保留线路拓扑、运行图和风险台账 |
| 普通公路 | 国省干线、路面、桥梁、边坡;资产评估与养护安排 | 病害、巡查、检测、工单、施工占道和通阻状态 | 3D 复核构件与地形;2D 管理线路分布、工单和年度计划 |
| 水运 | 航道、船闸、桥区、港区与船舶;通航条件及调度衔接 | 水位、净空、航道尺度、船舶动态、闸次和待闸队列 | 3D 表达船闸与港区关系;2D 展示电子航道图、队列和水位曲线 |
| 民航 | 航班、机位、场道、车辆和保障任务;空地协同 | 航班状态、资源占用、保障节点、超时和气象 | 3D 理解场面空间与回放;2D 展示航班时间线、里程碑与异常清单 |
| 邮政交通 | 分拨场地、干线运输、网点和末端;积压传播与场地安全 | 处理节点、到离场、积压、设备状态和安全隐患 | 3D 定位场内设备与动线;2D 分析网络流向、时效和节点积压 |
公路资料明确了设施检查、养护和检测等管理对象;铁路资料强调风险分级与隐患治理;水运资料列出了航道监测对象和联合调度方向;民航试点涉及空地联合态势与保障协同;邮政案例覆盖分拨场地巡查及隐患闭环。这些资料支持的是业务范围,不能直接证明某套大屏已具备相应能力。
国务院(交通运输部公开)|公路安全保护条例(2011-03-07)(xxgk.mot.gov.cn/jigou/fgs/2…
国家铁路局|国家铁路局关于印发《铁路安全风险分级管控和隐患排查治理管理办法》的通知(2023-03-10)(www.nra.gov.cn/xxgk/gkml/z…
交通运输部·水运局|交通运输部关于加快智慧港口和智慧航道建设的意见(2023-12-04)(xxgk.mot.gov.cn/jigou/syj/2…
中国民用航空局|关于在成都双流机场开展智慧民航建设全流程运行场景试点工作的通知(2024-03-04)(www.caac.gov.cn/XXGK/XXGK/T…
河北省邮政管理局·雄安新区邮政管理局|雄安局强化远程监控巡查(2026-08-14)(he.spb.gov.cn/hbsyzglj/c2…
🍋02|选择智慧高速:从一张设计稿确定开发对象
本次选定的开发基准是「智慧高速运行监测中心」。画面以多层互通为中心,周围配置节点流量、事件处置、车型结构、设备在线率和趋势对比。这张方案同时包含空间关系、统计信息与对象操作,可将设计到代码的过程拆为清晰的实现任务。
选定开发设计稿|智慧高速运行监测中心。中央互通、五个点位及四周业务面板构成后续布局和交互的基准。
预览视频:
中央多层道路需要重建曲线、桥面高度与车道边界;行驶车辆需要模型资产、连续姿态和可查询的对象标识;五个点位需要连接相机目标与屏幕标签;周边图表则需要统一统计口径。由此形成“道路怎样构建—车辆怎样运动—点击后怎样关联信息与视角”的开发主线。
实现范围:当前项目是可交互的前端演示。业务面板使用固定快照,车辆采用程序生成的轨迹;道路由选定设计稿重建,未接入测绘路网、实时交通数据或实际调度指令系统。文中编号 V0869、P001 等均为演示对象。
开发结果|智慧高速运行监测中心白天总览。中央为程序生成的三维路网与车辆,四周为快照驱动的流量、事件、车型和设备面板。
🍓把选定画面对应到高速运行管理任务
交通运输部公布的江苏“AI+智慧路网”案例,将营运单位、交通执法、高速交警纳入协同指挥,并汇聚气象、基础设施和应急资源等数据。对应到系统设计,发现事件、研判影响、签发指令、现场执行和确认恢复,需要不同的状态与权限。地图上出现一个告警点,不代表调度流程已经完成。
交通运输部(案例提供方:江苏省交通运输厅)|交通运输部发布12项交通强国建设试点典型案例——案例2“AI+智慧路网”智护高速公路平安通畅(2026-08-26)(www.mot.gov.cn/xinwen/jiao…
| 指标 | 必须明确的口径 | 本项目的实现 |
|---|---|---|
| 今日通行 | 车次或独立车辆;统计范围、断面与去重方式 | 快照中节点流量求和为 48,000;与场景车辆数独立 |
| 平均速度 | 道路范围、时间窗口、来源与聚合方法 | 读取固定快照的 88 km/h;不由画面车辆反推 |
| 设备在线率 | 设备清单、心跳有效期、维护停机和无数据状态 | 固定数据合计 990/1,000,得到 99.0% |
| 处理中事件 | 事件去重与状态机;开始、结束和恢复条件 | 按快照状态筛选出 3 起;未接入真实派单流程 |
四个指标的时间尺度也不同。车辆位置属于轨迹观测,断面流量属于窗口统计,设备在线依赖心跳,养护状态可能来自工单。生产接口需要同时携带观测时间、接收时间、来源与质量标记。浏览器动画流畅,只说明渲染连续,不能证明数据实时。
🍋03|用 Codex 推进开发:从设计图拆到工程边界
工程拆分采用“业务需求—设计基准—资产拆分—场景实现—交互验证”的路径。Codex 承担设计生成、代码编写和检查等执行工作;每一步的可用结果仍需要由对应产物证明,例如布局配置、模型文件、可操作页面与测试记录。
| 步骤 | 落地产物 | 需要验证的问题 |
|---|---|---|
| 行业与场景拆解 | 对象、指标、事件、使用者及数据边界 | 指标是否回答真实业务问题;是否混用不同统计口径 |
| 选择设计基准 | 整屏选定设计稿与固定画布 | 标题、侧栏、场景和趋势区域的比例是否确定 |
| 拆分视觉资产 | 背景、标题、边框、图标、点位资源 | 一个边框由哪个容器绘制;内部图表可用区域有多大 |
| 建立三维场景 | 道路曲线、路面、车辆、地形和相机 | 空间是否连续;道路、标线、车辆是否采用同一车道基准 |
| 连接交互 | 点位定位、单车选择、跟随、昼夜与暂停 | 选择的对象、信息面板和相机目标是否一致 |
| 验证与交付 | 代码测试、运行截图、检查记录 | 功能、视觉一致性与目标设备性能是否分别验收 |
页面采用 1672 × 941 的设计画布;中央场景参照区域为 x=395、y=178、宽 881、高 462。最外层按浏览器宽高的较小比例统一缩放,使标题、面板和场景共享缩放关系。标题背景、面板底图与内容层各自承担明确职责,避免重复叠框。
代码 1|整屏等比缩放
const scale = Math.min(size.w / 1672, size.h / 941);const stageStyle = { left: (size.w - 1672 * scale) / 2, top: (size.h - 941 * scale) / 2, transform: `scale(${scale})`,};
这段代码按原实现抽取了 style 表达式,便于说明缩放关系。它保持整屏比例,不会自动改成移动端卡片布局;当目标屏幕宽高比不同时,会出现居中留白。图表内部仍需分配绘图区、坐标轴、图例和数值区,不能仅把一个小图表塞进大边框。
当前流量条形图、车型环图和趋势图由 React/SVG 实现,使用各自的 viewBox 与数据映射;事件和设备区域使用表格。虽然依赖清单含 ECharts,不能据此把这些业务图表写成 ECharts 实现。
🍋04|渲染架构:业务状态与逐帧计算分开
| 模块 | 职责 |
|---|---|
| main.tsx/default-dashboard.json | 注册业务块、导入布局、设置整屏缩放 |
| state.tsx | 保存节点、车辆、跟随、昼夜、暂停和流光开关等 UI 状态 |
| ExpresswayScene.tsx | 创建/销毁场景,将 React 状态传给 Three.js 运行时 |
| sceneLayout.ts/createExpressway.ts | 选定设计稿坐标重建、道路与环境几何、渲染循环和资源释放 |
| traffic.ts/vehicleModel.ts | 加载 GLB 与贴图、计算车辆姿态、更新实例矩阵和处理拾取 |
| sceneEffects.ts/cameraRig.ts | 道路与点位效果、目标相机、车辆跟随和标签投影 |
React 管理业务选择和页面状态;Three.js 在 requestAnimationFrame 中更新相机、车辆和材质 uniform。普通运行时,车辆信息约每 500 ms 回传到 React,避免把每帧的所有实例矩阵写入组件状态。这个频率是界面信息刷新策略,不是后端数据采样频率。
渲染循环使用受上限约束的 dt 推进 elapsed,暂停时冻结 elapsed,车辆、道路流光和水面共享这个动画时钟。相机过渡与昼夜切换仍可使用 dt 完成操作反馈。页面隐藏后停止循环,卸载时取消动画、移除监听器并释放几何、材质、纹理和渲染器。
🍋05|道路重建:屏幕坐标、曲线与车道共用一个基准
🍓选定设计稿逆投影,解决主视角构图对齐
设计图只提供二维像素位置。项目先固定透视相机,再把选定设计稿上的道路控制点转成标准化设备坐标(NDC),发出射线,与指定高度的水平面求交。每个控制点包含 x、y 和人工设定的 height;由这些三维点生成 Catmull-Rom 曲线。
代码 2|由设计稿像素得到场景控制点
const ray = new THREE.Raycaster();ray.setFromCamera(new THREE.Vector2( (x - SCENE_RECT.x) / SCENE_RECT.width * 2 - 1, 1 - (y - SCENE_RECT.y) / SCENE_RECT.height * 2), camera);const point = ray.ray.intersectPlane( new THREE.Plane(new THREE.Vector3(0, 1, 0), -height), new THREE.Vector3());if (!point) throw new Error('Reference point is above the reconstruction plane');
这相当于为二维像素补充一个深度约束:平面方程 y − height = 0 决定射线落点。它能让总览视角中的道路走势接近选定设计稿,但单张图不足以恢复唯一的真实几何。相机切到侧面后,还需要检查桥面高差、交叉关系和连接连续性。接入真实路网时,应改用经过坐标与高程处理的道路数据。
API 依据|Three.js Raycaster:NDC 与相机射线(threejs.org/docs/pages/…
🍓路面、标线、车辆和流光保持同一横断面
道路采用沿曲线扫掠的带状网格。每个采样点取曲线位置 p 和切线 d,用水平侧向量 (−d.z, 0, d.x) 将道路展开到左右边界,再把相邻横断面连接为两个三角形。UV.x 对应横向位置,UV.y 对应沿线的归一化弧长位置。
代码 3|路面横断面与 UV 的生成
for (let i = 0; i <= segments; i++) { const t = i / segments; const p = curve.getPointAt(t), d = curve.getTangentAt(t); const side = new THREE.Vector3(-d.z, 0, d.x).normalize(); for (const k of [-1, 1]) { const q = p.clone().addScaledVector( side, offset + k * (typeof width === 'function' ? width(t) : width) / 2 ); verts.push(q.x, q.y + height, q.z); uv.push((k + 1) / 2, t); }}
carriageway() 统一定义中央分隔、道路边缘、车道宽度与各车道中心。车辆偏移、道路标线和 Shader 采样均使用该基准,可减少车辆压线、流光越过护栏等错位。当前侧向量按水平面计算,没有建立带超高的完整道路横断面;它适合本演示的构图重建,不等同于道路工程设计模型。
🍋06|道路 Shader:窄亮带与短光包怎样形成
道路效果包含两层:MeshStandardMaterial 保留沥青表面和基础光照;独立 ShaderMaterial 在道路上方绘制流光。几何高度分别为道路曲线之上约 0.03 和 0.06 个场景单位,减少共面闪烁。流光保留深度测试,关闭深度写入,并采用加法混合,因此不会替代道路本身的材质。
片元着色器首先把横向 UV 转成道路空间位置 across,再根据中央分隔和车道宽度求出车道内局部坐标 x。沿线距离 travel 由 UV.y × roadLength 得到,direction 区分两侧车流方向。这样,动画的周期按道路长度组织,而不是把所有道路强行压成同一张平面动画。
代码 4|沿车道移动的短光包
float across = (vUv.x - .5) * width;float lane = (abs(across) - median) / laneWidth;float x = (fract(lane) - .5) * laneWidth;float direction = across > 0.0 ? 1.0 : -1.0;float laneIndex = floor(lane);float seed = laneIndex * 23.71 + direction * 11.3;float travel = vUv.y * length * direction;float phase = mod(travel - time * speed + seed, 61.0 + laneIndex * 8.0);float tail = smoothstep(0.0, 6.0, phase) * (1.0 - smoothstep(6.1, 7.8, phase));float headDistance = (phase - 6.0) / .85;float head = exp(-headDistance * headDistance);float aa = max(fwidth(x), .035);float core = 1.0 - smoothstep(.055, .055 + aa, abs(x));
phase 控制光包沿道路循环;tail 用两段 smoothstep 形成短尾迹;head 用高斯形状集中亮度;core 把能量收束到车道中心的细线。fwidth 提供与屏幕像素变化有关的过渡宽度,用于减轻细线远距离观察时的锯齿。以上节选依赖完整着色器中的 varying、uniform 与最终颜色输出。
代码 5|限制发光范围并保留连续细带
float halo = exp(-x * x / .18);float mask = step(median, abs(across)) * (1.0 - step(edge, abs(across)));float ribbonDistance = abs(abs(across) - (median + laneWidth * .72));float ribbonCore = 1.0 - smoothstep( .045, .045 + max(fwidth(across), .04), ribbonDistance);float ribbonHalo = exp(-ribbonDistance * ribbonDistance / .16);float wave = .35 + .65 * pow(.5 + .5 * sin(travel * .035 - time * .8), 3.0);float packet = core * tail * .55 + halo * (tail * .25 + head * .45);float ribbon = (ribbonCore * .72 + ribbonHalo * .24) * wave;float alpha = (packet + ribbon) * mask * flowEnabled;
mask 把效果限制在有效路面内;packet 负责离散短光包;ribbon 负责两侧靠近快车道的连续细带;wave 调整连续带的明暗节奏。单独加粗光带会遮住标线,而把整条路染亮会削弱层次。因此,宽度、光晕和透明度需要分别控制。
项目还为部分行驶车辆绘制跟随车身矩阵的短尾迹。道路光包和车辆尾迹不是同一机制:前者由 time、speed 和道路 UV 决定,后者跟随具体车辆。当前道路流光没有根据实测流量、拥堵等级或单车速度调整,不能将它解释为真实交通流仿真。
🍋07|车辆实现:模型外观、运动轨迹与身份关联
🍓模型资产:按车型加载,按部件实例化
小客车、货车、客车分别加载 sedan.glb、truck.glb、bus.glb。资产转换包含尺寸和前向轴归一化、车轮轴心分离、材质调整及贴图处理。小客车来源为 Comrade1280 的 Generic passenger car pack,许可为 CC BY 4.0;货车与客车来源为 Rgsdev 的 Free Low Poly Vehicles Pack,许可为 CC0。项目保留作者、来源、许可和修改说明。
模型来源|Comrade1280:Generic passenger car pack(Sketchfab)(sketchfab.com/3d-models/g…
模型来源|Rgsdev:Free Low Poly Vehicles Pack(OpenGameArt)(opengameart.org/content/fre…
运行时遍历模型内部 Mesh,每个车型的每个部件建立一个 InstancedMesh。车身、车轮和灯具保留独立部件,因此车轮可以旋转,灯具也可以随夜景增强。实例化减少重复模型的绘制调用,但调用次数仍与部件及材质数量有关,不会把整个车队自动变成一次 draw call。
API 依据|Three.js InstancedMesh:实例矩阵与绘制调用(threejs.org/docs/pages/…
车轮转角按 θ = elapsed × speed ÷ radius 推进,即用行驶距离除以轮胎半径。该实现依赖资产转换后的轴向和尺寸约定。外置小客车纹理在当前 FBX→GLB 转换链中使用 flipY=true;这属于本项目资产的修正,不能直接套用到所有 glTF 纹理。
🍓弧长运动:速度不依赖控制点分布
代码 6|计算车辆的道路位置与朝向
if (vehicle.stationary) { return { t: .5, p: vehicle.stationary.position.clone(), rotation: vehicle.stationary.rotation.clone() };}const t = wrap(vehicle.phase + elapsed * vehicle.speed / vehicle.road.length * vehicle.direction);const p = vehicle.road.curve.getPointAt(t);const forward = vehicle.road.curve.getTangentAt(t);p.addScaledVector( new THREE.Vector3(-forward.z, 0, forward.x).normalize(), vehicle.offset);p.y += vehicle.kind === 'car' ? .03 : .015;forward.multiplyScalar(vehicle.direction);const right = new THREE.Vector3().crossVectors(new THREE.Vector3(0, 1, 0), forward).normalize();const up = new THREE.Vector3().crossVectors(forward, right).normalize();return { t, p, rotation: new THREE.Quaternion().setFromRotationMatrix( new THREE.Matrix4().makeBasis(right, up, forward)) };
elapsed × speed ÷ road.length 将行驶距离换算为归一化进度,wrap 把进度循环到 [0,1)。getPointAt() 按曲线弧长映射取点,可减轻控制点疏密造成的速度变化;getTangentAt() 提供方向,再与侧向和上方向构造车辆姿态。这里的“等速”仍受曲线弧长采样精度影响。
API 依据|Three.js Curve:getPointAt 与 getTangentAt(threejs.org/docs/pages/…
同一车道中的车辆采用相同速度,以保持既定间距;当前没有跟驰、超车、换道、拥堵传播和匝道合流模型。主线端点延伸到允许视角之外,匝道端部用透明度渐变减轻循环突变。车辆沿三维道路运动已实现,但不能据此声称完成交通微观仿真。
界面速度使用 speed × 3.6 展示为 km/h,含有场景单位按米解释的演示约定;由于道路不是测绘模型,这个速度不是实车测量值。distance 来自 elapsed × speed,也是合成行驶距离。接入生产轨迹后,位置、速度和里程应回到各自的数据来源与坐标基准。
运行截图|选中 V0869 后显示车型、道路与演示速度。信息来自当前车辆对象和同一套姿态计算,没有关联真实车牌或实测轨迹。
🍓拾取映射:instanceId 不等于稳定车辆 ID
每帧先把可见车辆写入按车型划分的 slots,再按 slot 更新实例矩阵。相机移动后,可见对象会变化,因此 instanceId 是当前渲染批次的序号,不能直接当作业务车辆 ID。拾取需要用“车型+实例序号”查回 slots 中的车辆。
代码 7|将射线命中的实例关联到车辆实体
const meshes = parts.map(p => p.mesh);meshes.forEach(m => m.computeBoundingSphere());const hit = raycaster.intersectObjects(meshes, false) .find(h => h.instanceId !== undefined);if (!hit || hit.instanceId === undefined) return null;const vehicle = slots[hit.object.userData.kind as VehicleKind][hit.instanceId];return vehicle ? { id: vehicle.id, distance: hit.distance } : null;
模型更新后重新计算包围球,避免动态实例使用过期边界。命中车辆后,外层还比较部分不透明场景网格的距离,过滤被这些网格遮挡的对象;这不是覆盖全部透明物、实例化环境和复杂场景的通用遮挡方案。最终返回稳定 ID,信息面板与跟随相机均按该 ID 查找车辆。
停车车辆使用独立 stationary 分支,位置和姿态不随 elapsed 改变,并输出 0 km/h、0 行驶距离。这样能让停车状态与主路运动明确区分,也便于用时间推进后的重复拾取验证对象身份。
运行截图|服务区 P001 驻车近景。停车对象同样使用 GLB 资产与实例拾取,stationary 分支保持姿态不变,信息面板显示停车状态。
🍋08|点位、相机与昼夜:同一对象跨视角保持一致
点位由三维底盘、光柱和投影到 DOM 的标签共同组成。底盘 Shader 以半径构造圆环,以角度和时间构造断续旋转弧,再叠加扩散脉冲;选中状态改变颜色和强度。标签则使用 Vector3.project(camera) 从世界坐标转为场景内像素坐标。
这种拆分让文字保持清晰,同时让标记底部跟随三维位置。当前底盘和光柱关闭深度测试,以保证跨桥面可读;它们是语义覆盖标记,不是物理灯光,也不能依据它们是否被遮挡判断真实现场可见性。
代码 8|进入跟车视角的过渡与持续跟随
progress = Math.min(1, progress + dt / 1.35);if (progress < 1) { const t = progress * progress * (3 - 2 * progress); camera.position.lerpVectors(from.position, desired, t); target.lerpVectors(from.target, look, t);} else if (instantTrack) { camera.position.copy(desired); target.copy(look);} else { camera.position.lerp(desired, 1 - Math.exp(-dt * 7)); target.copy(look);}camera.lookAt(target);camera.updateMatrixWorld();
进入跟车时使用约 1.35 秒的 smoothstep 过渡;稳定跟随后,位置以 1 − exp(−7dt) 为插值系数,降低帧间隔变化对平滑速度的影响。节点视角同样保存 position 和 target 两组端点,并支持在过渡中重新选择目标。上述代码是跟车分支节选,节点分支使用相同的缓动形式。
选择节点会清除车辆选择;选择车辆会退出固定点位视角。跟随对象发生路径循环时,运行时识别归一化进度的大幅跳变,结束本轮跟随并回到总览,避免相机跟随对象瞬间跨越路网。
运行截图|东枢纽视角。相机目标、点位标签投影与三维场景同步更新,周围业务面板保留原有布局。
昼夜切换由连续变量 night∈[0,1] 驱动,统一插值环境光、太阳光颜色与强度、背景、雾、环境贴图强度、路灯、玻璃发光和车辆灯光。夜景没有仅通过叠加黑色遮罩实现;曝光还略有提高,以保留路面和车辆轮廓。
代码 9|用同一个夜景参数协调光照
night = THREE.MathUtils.damp(night, nightTarget, 4, dt);effects.night.value = night;ambient.intensity = THREE.MathUtils.lerp(1.3, .45, night);sun.intensity = THREE.MathUtils.lerp(3.1, .45, night);renderer.toneMappingExposure = 1.1 + night * .12;scene.environmentIntensity = .035 * (1 - night * .7);lampMaterial.emissiveIntensity = .1 + night * 3.5;glass.emissiveIntensity = night * .6;serviceLights.forEach(light => { light.intensity = night * 260; });
节选省略了颜色、背景与雾的插值,以及接近目标时直接吸附的分支。车辆头灯使用实例化平面光束,路面灯池由材质计算,服务区另有灯光对象;它们并非为每辆车配置真实投射阴影的独立灯源。完整光照效果与运行开销,需要在目标 GPU 上共同评估。
运行截图|夜间总览。夜景参数同时作用于环境、路面、设施及车辆灯光;业务面板仍显示同一演示快照,不代表截图采集时刻的真实交通。
🍋09|从演示到运行系统:需要补齐哪些数据能力
视觉模型、连续运动、模型拾取与演示对象关联、真实业务数据是四项独立能力。当前项目实现了前三项;生产接入还需要轨迹来源、时空匹配、数据质量、事件状态及操作回执。以下是接口设计要求的拆解,不是本项目已经实现的后端功能。
| 层次 | 接口至少需要说明的信息 | 展示与处理策略 |
|---|---|---|
| 实体与空间 | entityId、实体类型、roadId、方向、laneId、坐标系及高程基准 | 统一业务 ID;完成坐标转换后再绑定模型与面板 |
| 时间与来源 | observedAt、receivedAt、source、质量状态、有效期限 | 展示数据截至时间;过期后保留最后已知状态或停止外推 |
| 轨迹 | 连续观测点、采样间隔、速度来源及定位质量 | 插值用于平滑,不把插值位置标为新观测;断流单独处理 |
| 统计 | 时间窗口、断面范围、聚合与去重方法 | 分别计算流量、速度和车辆数,不用场景实例数代替运营统计 |
| 事件 | eventId、等级、检测与确认时间、责任单位、状态、证据和回执 | 呈现检测、确认、派发、执行、恢复的状态迁移 |
| 操作 | 用户角色、授权范围、指令编号、确认与执行结果 | 将查看、建议和指令执行区分;失败与未回执状态可见 |
接入真实车辆信息时,公开大屏与业务终端还需要不同的信息粒度。展厅页面可使用模拟对象;业务页面按任务展示匿名目标 ID、类别、最后观测时间和数据来源。真实车辆轨迹涉及个人信息边界,实际处理依据、权限与保存要求需要按适用场景另行确定;前端显示一个编号并不等于完成合规处理。
🍋10|最后
从当前的智慧高速演示项目出发,我们需要把可视化大屏的整个生产链路继续深化。核心方向是:重交互、轻性能、动画流畅 的可视化智能平台建设。
🍋第一阶段:交互与布局基础的统一
我们已经完成了拖拽 npm 库的基础开发,现有大屏都建立在这个基础上。但暴露出来的问题很明显:
- 编辑模式与预览模式的布局资源不统一 :拖拽时的布局数据、预览时的渲染逻辑、导出时的坐标映射,三者之间存在割裂。需要统一底层布局模型,让所有模式共用一套坐标与尺寸基准
- 组件状态同步的复杂度 :每个组件在拖拽中被改动后,需要同步到预览、同步到数据结构、同步到导出格式,流程冗长且容易出错
解决思路 :建立统一的布局引擎。编辑、预览、导出都围绕这个引擎运转,减少模式间的数据转换成本。
🍋第二阶段:组件库与图表库的沉淀
智能可视化的核心就是自己的组件库 ,这是一个长期的过程。
开源组件库:
我做的平台组件库(正在构建)
现状 :
- 已经预留了一些扩展口子
- 接入了 ECharts 资源库与 Three.js 官网组件资源
- 自己做了一套组件库,但还不够理想
需要做的 :
- 图表组件库的深化
-
* 柱状图、折线图、散点图等基础图表要有统一的数据接口- 根据前面积累的设计稿中的数据样本,不断完善图表的视觉效果和交互反馈
- 建立图表配置的最小化 API,让平台能自动推荐最合适的图表类型
- 三维组件的集成
-
* 基于 Three.js 官方资源,包装出易用的 3D 场景容器、道路、车辆、流光等通用模块- 三维组件要支持拖拽调整视角、相机预设切换、对象拾取等交互
- 函数库的建立与反馈
-
* 为每个组件建立计算函数库(坐标映射、数据聚合、动画插值等)- 把这些函数喂给平台学习 ,让平台能理解"这个数据适合用什么图表、什么配置"
🍋第三阶段:可视化智能体平台
有了统一的组件库和函数库,下一步就是让平台具备智能生产能力 。
核心能力 :
- 自动推荐 :用户给出业务场景和数据源,平台自动推荐合适的组件组合与布局方案
- 智能配置 :根据函数库和历史设计稿的样式偏好,自动生成初稿配置,用户调整细节即可
- 批量生产 :同一个大屏模板,能快速适配不同的数据源、业务对象和地域,实现模板化生产
这一阶段相当于把设计知识和技术决策沉淀成平台的学习材料,降低每个新项目的从零开始的成本。
🍋最终目标:交互革新,用户畅享
所有这些工作的指向都是同一个初心:
用可视化与自动化技术的应用,提升用户体验,让用户在网页上畅享流畅、智能的交互体验。
具体体现在:
- 流畅 :拖拽无卡顿、动画帧数稳定、三维场景响应迅速
- 智能 :平台能理解业务需求,快速生成高质量方案,减少用户的重复劳动
- 可用 :组件库成熟,接口统一,用户无需深入学习就能上手构建大屏
这不只是技术堆积,而是围绕用户体验 和生产效率 的完整闭环。每一步都要验证实际效果。
说了很多接下来的构想,回到正题。我把上面的设计稿和智慧高速3D源码整理了一下.
如果你需要源码参考的话,可以来 柳杉前端 公众号 同名文章自取哈。