基于Three.js的仓库可视化管理系统技术拆解与性能优化实践 简介这是一套基于Three.js构建的仓库三维可视化管理系统的完整前端源码面向Web前端开发者、3D可视化学习者及智慧仓储系统实施人员解决传统二维仓库管理系统空间感知弱、交互性差、数据呈现不直观等痛点。资源共89个文件包含37个JavaScript核心逻辑与Three.js渲染脚本、20个Vue组件实现模块化UI与状态管理、2个GLB格式3D模型货架与货物、8个PNG纹理贴图及配套JSON配置与XML元数据整体压缩包仅3.43MB轻量易部署。已有1933人学习下载适合中高级前端工程师深入理解WebGL场景搭建、实时数据绑定、3D交互控制及性能优化实践。源码采用标准Vue CLI工程结构含完整路由、状态管理Vuex、API封装与three.js初始化流程目录清晰、注释充分可直接运行调试并快速扩展入库动画、路径规划、库存热力图等企业级功能。 去年接了一个仓库管理相关的项目客户提的需求很直接要一个能“看到整个仓库”的 3D 大屏不是平面图不是饼图柱状图是那种能旋转、能点货架、能看到货物堆在哪里的 3D 场景。我当时想的是这不就是给管理页面套个三维皮肤嘛但真做起来才发现基于 Three.js 的仓库可视化管理系统难点不在“画得好看”而在怎么把 WMS 的业务数据准确映射到三维场景里还要保证大仓库不卡、点击拾取不飘、坐标不错位。这篇文章就把这套系统的技术拆解和踩坑经历完整写出来适合正在做仓储可视化、智慧园区大屏或者想拿 Three.js 做正经业务项目的人参考。1. 我为什么要在仓库系统里上三维可视化1.1 表格里的库位永远不够直观传统 WMS 系统里库存数据长什么样一个库位编号比如 A-03-02-01后面跟着 SKU 编码、批次号、数量、入库时间。数据没问题但人脑要从一串编码里想象出“这个货架第三层第二格还剩多少空间”是非常反直觉的。仓库管理员的日常工作里大量决策依赖空间感知哪个区域快满了、哪个巷道被临时摆放的货物堵住、某个热销 SKU 分布在多少个库位。表格能回答“有没有”但很难回答“在哪里”“是不是顺路”。我调研过的客户里不少团队的管理方式是靠老师傅记忆或者定期去现场拍照。这在小仓库勉强够用一旦上了立体库、多楼层、几千个库位光靠表格和记忆就会出问题。1.2 可视化之后业务到底获得了什么把仓库场景搬进浏览器之后业务上的提升是能直接感知到的。第一是空间利用率一目了然。整个仓库的鸟瞰视角下库位按占用率着色哪些区域长期空置、哪些区域爆仓一眼就能看出来。运营排位的时候可以直接在三维场景里找空位而不是在表格里一个一个筛。第二是异常定位速度变快。温湿度传感器告警、某个库位库存冻结、货架超期未动这些信息通过颜色闪烁或告警标签挂在对应三维模型上管理人员就不用再靠“先查库位号再翻地图”的方式定位。第三是可远程巡检。疫情之后很多园区不允许无关人员频繁进出仓库三维场景配合摄像头点位和传感器数据能实现一定程度的远程巡检。虽然不能完全替代现场但日常巡查频次可以明显下降。1.3 三维可视化和“大屏好看”是两回事我必须提醒一句三维可视化管理系统核心是“管理”不是“可视化”。如果只是做几个旋转的货架模型、加一圈流光粒子那叫宣传片不叫管理系统。真正能落地的系统一定是数据驱动的。货架上的箱体模型必须对应真实库存记录库位颜色必须反映真实的占用和冻结状态设备动画必须挂接真实的状态上报。换句话说Three.js 在这里的角色是渲染引擎它把数据库里的记录“翻译”成空间里的物体翻译错了画面再漂亮也没有用。这也是我在这套系统里坚持“配置驱动场景、数据驱动状态”的原因后面会详细讲。2. 技术选型复盘Three.js 是主力渲染引擎的理由2.1 先后对比过几条技术路线动手之前我列了几个候选方向。这个项目既要跑在浏览器里又要能快速迭代所以技术选型很关键。Unity 和 Unreal 的效果当然好光影和物理引擎都强但它们的产物通常需要下载客户端或者使用 WebGL 导出打包体积大、前端集成成本高。对于一个以数据管理为核心的业务系统来说这个代价不太划算。Cesium 是地理空间可视化的老牌选手针对的是地球级场景处理倾斜摄影、地形、经纬度数据非常强。但仓库是室内场景空间尺度小、精度要求高用 Cesium 反而有点“杀鸡用牛刀”而且它的事件机制和场景管理更偏 GIS 风格跟常规前端开发习惯有差异。纯 2D Canvas 加 CSS 3D 的方案我也考虑过做平面示意图可以用但用户需要旋转视角、缩放检查货架细节、逐个拾取库位2D 方案在交互上先天受限。最终选了 Three.js原因是它在“浏览器实时 3D”这个区间里生态最成熟、社区样本最多、学习曲线最平缓。方案画面表现前端集成难度室内精细交互包体/运行时成本Unity WebGL很强高需跨技术栈协作强但开发重运行时较大加载慢Cesium场景大适合GIS中室内精度一般中等偏大原生 WebGL灵活高所有底层代码自己写取决于实现可以很小CSS 3D / 2D Canvas一般低弱很小Three.js强低纯 JS 生态强可控按需引入2.2 Three.js 对前端团队最友好的地方如果团队里全是前端工程师没有专门的图形学专家Three.js 几乎是首选。它把 WebGL 里繁琐的着色器编写、缓冲区管理、矩阵运算都封装好了你只需要用 Scene、Camera、Renderer、Mesh 这些高层概念组织场景。依赖安装也简单npm install three 就完事了配合 Vite 或 Webpack 都很顺畅。跟 Vue、React 这类框架结合没有强冲突页面里的统计面板、告警列表仍然用传统 DOM 渲染Three.js 只管 Canvas 里的三维部分。而且 Three.js 有非常活跃的社区遇到问题搜索一下基本都能找到对应的 issue 或示例。这个对项目排期来说太重要了。2.3 这套系统里真正用到的 Three.js 核心能力这里说说项目里实际高频用到的模块列出来给刚入坑的人一个方向。WebGLRenderer渲染器负责把场景画到 canvas 上需要设置抗锯齿和像素比。Scene场景图所有模型、光源、辅助对象的容器。PerspectiveCamera透视相机模拟人眼近大远小的效果仓库这种室内场景用透视相机比正交相机自然得多。OrbitControls轨道控制器鼠标拖动旋转、滚轮缩放这套交互对仓库查看非常顺手。Raycaster射线拾取实现鼠标点击“命中”某个货架或库位的核心。InstancedMesh实例化网格用一次绘制渲染大量相同几何体的关键后面性能优化会重点讲。GLTFLoader加载外部模型用于 AGV 小车、叉车、机械臂等精细模型。TWEEN / 自写插值相机飞行、AGV 移动路径动画我用的是 tweenjs/tween.js简单场景也可以自己写 lerp。掌握了这些仓库可视化的主体功能基本就能搭起来了。真正拉开差距的是业务映射和性能细节下面从源码目录开始讲。3. 源码目录规划一套可视化仓库系统怎么拆模块3.1 目录结构与模块职责拿到源码包之后第一件事别急着跑先看目录结构。这套系统的代码组织方式直接决定了你二次开发的时候是“改一改就能用”还是“到处找代码”。我给出的目录设计大致是这样warehouse-visual/ ├── index.html ├── package.json ├── vite.config.js ├── public/ │ ├── models/ │ │ ├── agv.glb │ │ ├── fork.glb │ │ └── shelf.glb │ ├── textures/ │ │ ├── floor.jpg │ │ └── tag.png │ └── data/ │ ├── layout.json │ └── mockStock.json └── src/ ├── main.js ├── core/ │ ├── renderer.js │ ├── scene.js │ ├── camera.js │ └── controls.js ├── config/ │ ├── warehouse.js │ └── colorMap.js ├── loaders/ │ ├── modelLoader.js │ └── dataService.js ├── business/ │ ├── rackManager.js │ ├── stockManager.js │ └── deviceManager.js ├── interaction/ │ ├── picker.js │ ├── searchFly.js │ └── tooltip.js ├── utils/ │ ├── coordinate.js │ └── logger.js └── widgets/ ├── statPanel.js └── alarmList.jscore 这层管 Three.js 基础设施渲染器、场景、相机、控制器。business 这层管仓库业务模型货架怎么生成、库存怎么映射成三维物体、设备怎么更新位置。interaction 这层管用户操作点击拾取、搜索飞行、悬浮提示。widgets 是传统的 DOM 浮层用来展示统计数据和告警列表。这个分层有个好处业务层不直接依赖 Three.js 的渲染细节数据层只负责拉取和推送数据。将来哪怕你想把渲染器从 Three.js 换成别的引擎只要 business 层暴露的接口不变上层交互可以不动。3.2 仓库配置驱动场景不要硬编码货架坐标我看到过不少项目货架位置是直接在代码里写死的const rack createRack(); rack.position.set(10, 0, 20);这样写问题很大。仓库的库区布局、货架数量、行列数在运营过程中是会调整的。一旦调整你就要改代码、重新打包部署这个频率和成本完全不可接受。我这套系统的做法是所有布局信息放配置文件里场景根据配置动态生成。仓库的长宽、巷道的宽度、货架的行列层数、每个库位的尺寸全部由 layout.json 驱动。{ warehouse: { name: 东部一号库, length: 120, width: 80, unitScale: 1 }, areas: [ { id: A, name: A区-常温存储区, position: [5, 0, 60], rotation: 0, rows: 3, columns: 20, levels: 4, laneWidth: 2.5, rack: { width: 2.4, depth: 1.2, height: 1.8, beamThickness: 0.08 } } ], devices: [ { id: AGV-001, type: agv, start: [8, 0, 62] } ] }配置驱动生成场景的好处是换一个仓库只需要换 layout 文件。我在项目里做过一个演示版客户提供了一个新库区的平面草图我在配置文件里把区域数据改了一遍十分钟后浏览器里就是新仓库的完整三维场景这个体验对交付来说是加分项。3.3 渲染层、业务层、数据层分离具体落地的时候我会把“拉取数据”“计算状态”“更新渲染对象”这几件事严格分开。数据层是最容易被忽视的。很多人在数据刷新时直接改 Three.js 模型的位置和颜色短期内没问题时间一长代码就会变成一团乱麻。我建议给数据层定义几个明确的接口// dataService.js export async function fetchWarehouseLayout() { ... } export async function fetchStockSnapshot() { ... } export function subscribeStockUpdate(callback) { ... } export function subscribeDeviceStatus(callback) { ... }业务层拿到数据之后把库存记录转换成“库位状态对象”{ rackId: A-03, level: 2, slot: 4, status: occupied, // empty | occupied | frozen | abnormal sku: SKU10086, quantity: 120, updatedAt: ... }渲染层只做一件事根据状态对象更新对应三维物体的外观。这样每次数据刷新你不需要关心数据是从 WebSocket 推的还是轮询拉的只要状态对象没有变渲染层就不需要做任何操作。4. 核心功能实现拆解从货架生成到点击拾取4.1 程序化生成一排排货架仓库场景里数量最多的物体就是货架。如果每个货架都单独建模再加载一个几千库位的仓库模型面数和文件体积都会爆炸。正确做法是用代码程序化生成并且用 InstancedMesh 来做批渲染。我先把货架抽象成一组几何体立柱、横梁、层板。粗略版货架模型可以用 BoxGeometry 组合function createRackGeometry(rackConfig) { const group new THREE.Group(); const columns []; const beams []; // 立柱 for (let i 0; i 4; i) { const col new THREE.BoxGeometry(0.06, rackConfig.height, 0.06); col.translate(...); columns.push(col); } // 层板 for (let level 0; level levels; level) { const beam new THREE.BoxGeometry(rackConfig.width, 0.04, rackConfig.depth); beam.translate(...); beams.push(beam); } const merged BufferGeometryUtils.mergeGeometries([...columns, ...beams]); return merged; }这里把立柱和横梁合并成一个 BufferGeometry整个货架就是一次 draw call。然后把同一个几何体放进 InstancedMeshconst rackMesh new THREE.InstancedMesh( mergedGeometry, sharedMaterial, totalRacks );然后用矩阵设置每个货架的位置和朝向const matrix new THREE.Matrix4(); rackList.forEach((rack, index) { matrix.setPosition(rack.x, 0, rack.z); matrix.setRotationFromEuler(new THREE.Euler(0, rack.rotation, 0)); rackMesh.setMatrixAt(index, matrix); });这样整个仓库几千排货架draw call 只有个位数。性能差距非常明显。4.2 库位状态颜色映射与动态刷新货架有了接下来把库位状态用颜色表达出来。每个库位可以对应一个小的立方体或扁平盒放在货架层板上。这些库位小盒同样用 InstancedMesh 生成通过 instanceColor 单独设置每个实例的颜色。颜色映射表放配置文件里方便业务方按自己的习惯调整export const STOCK_STATUS_COLOR { empty: 0x2ecc71, // 绿色空库位 occupied: 0xffa502, // 橙色已占用 frozen: 0xdfe4ea, // 灰色冻结 abnormal: 0xff4757, // 红色异常 reserved: 0x1e90ff // 蓝色预占 };数据刷新的逻辑是拿到 stockSnapshot遍历每个库位更新 instanceColor。如果只是局部更新不需要重建整个实例化网格用 setColorAt 刷一遍即可。function updateSlotColors(stockList) { stockList.forEach((item) { const instanceIndex getSlotInstanceIndex(item.rackId, item.level, item.slot); const color new THREE.Color(STOCK_STATUS_COLOR[item.status] || 0xffffff); rackSlotMesh.setColorAt(instanceIndex, color); }); rackSlotMesh.instanceColor.needsUpdate true; }更新颜色之后记得把 needsUpdate 置为 true否则 GPU 不会重新上传颜色数据。4.3 Raycaster 点击库位的完整链路点击拾取是三维仓库最核心的交互。用户点击画布上的某个位置程序需要判断点中了哪个货架、具体是哪一层哪个库位。链路分成三步第一步把鼠标的屏幕坐标归一化到 NDC 坐标const ndc new THREE.Vector2(); ndc.x (event.clientX / window.innerWidth) * 2 - 1; ndc.y -(event.clientY / window.innerHeight) * 2 1;第二步用 Raycaster 从相机发射射线raycaster.setFromCamera(ndc, camera); const intersects raycaster.intersectObjects(interactiveMeshList, true);这里有个细节intersectObjects 的第二个参数 recursive 设为 true是因为有些模型是嵌套结构比如货架模型里子节点很多。但递归检测会带来性能开销所以我在项目里把需要拾取的对象单独收进一个 interactiveMeshList不让射线去检测整个 scene。第三步从命中结果里取第一个有效对象并读取业务标记if (intersects.length 0) { const hit intersects[0].object; const slotInfo hit.userData.slotInfo; if (slotInfo) { openSlotDetailPanel(slotInfo); } }常用的做法是在创建库位小盒时把库位编号、SKU、数量这些业务信息塞进 userData拾取命中后直接拿出来渲染到 2D 浮层面板。4.4 搜索定位与飞行动画库存搜索是这个系统的另一个高频操作。输入框里输入 SKU 或批次号系统自动定位到对应的库位。实现上也没有那么复杂主要分三步第一步在业务层把库存表反向索引建立 sku → slotList 的映射。第二步算出目标库位的世界坐标function getWorldPositionOfSlot(rackId, level, slot) { const rack rackMap.get(rackId); const slotOffset getSlotOffset(rack, level, slot); return new THREE.Vector3( rack.position.x slotOffset.x, rack.position.y slotOffset.y, rack.position.z slotOffset.z ); }第三步让相机和控制器飞到这个坐标附近同时高亮目标库位。我用 tweenjs/tween.js 做一个平滑过渡动画时长控制在 600 到 800 毫秒太短会眩晕太长影响效率。new TWEEN.Tween(camera.position) .to(targetPosition, 700) .easing(TWEEN.Easing.Cubic.Out) .onUpdate(() camera.lookAt(target)) .start();对 3D 产品和 UI 动效比较敏感的人很容易忽视这一点三维相机移动和传统 UI 动画不太一样镜头不是简单平移而是要同时更新 lookAt 的视线方向。否则“飞行”过去之后画面中心不是目标物体体验会很别扭。5. 大仓库不卡顿渲染性能优化的关键动作5.1 Draw Call 是核心瓶颈仓库场景卡顿十有八九是 draw call 太多了。每提交一个物体给 GPU 绘制都是一次 draw call。一个普通的仓库场景货架几千、库位几万、托盘几百如果每个库位都是一个独立 Mesh帧率直接崩。我第一次把完整场景加载出来的时候renderer.info.render.calls 飙到两万多帧率不到 15。后来把所有静态几何体合并、改成 InstancedMeshdraw call 降到 200 以内帧率一下就上去了。优化前后效果对比如下优化项优化前优化后draw call20000180帧率15 fps60 fps内存占用模型重复加载几何体共享加载时长12s2s建议每个项目启动后在开发阶段打开 renderer.info盯着 draw call 数量做优化。5.2 材质与纹理的复用策略很多新手写 Three.js 时会犯同一个错误每个物体都创建新材质、新纹理。比如给每个库位状态小盒单独 setColor 时如果每个小盒都 new MaterialGPU 就会为每个材质生成一个渲染批次。正确做法是同类型的物体共用同一个材质颜色变化优先通过实例颜色 instanceColor 来实现而不是创建新材质实例。纹理也一样。仓库地面的纹理、货架贴图、标签贴图加载一次之后缓存复用。加载模型时如果模型内含多个材质尽量在建模阶段就合并材质减少运行时的材质切换。5.3 渲染细节分级与阴影取舍实时阴影是性能的一大杀手。仓库场景里几千个货架如果都开启实时阴影即使帧率没问题GPU 的负担也会很大。我的做法是大场景俯瞰模式下关闭阴影需要近距离查看货架细节的时候才开启指定区域的阴影。如果客户要求始终有阴影效果可以用一张静态的软阴影贴图贴在地面上模拟货架底部阴影视觉上够用性能开销几乎为零。LODLevel of Detail也可以派上用场。远景的货架可以用简化版几何体近景再换成精细几何体。不过仓库场景结构相对规整在 InstancedMesh 优化之后LOD 不是必须的。我建议先做实例化再评估是否需要 LOD不要一上来就堆复杂度。5.4 定时刷新与静态帧渲染仓库可视化还有一个容易被忽视的优化点大多数时候场景是静止的。用户不动鼠标、数据没变化的时候画面根本不需要每帧重绘。Three.js 默认的渲染循环是 requestAnimationFrame 持续调用 render()。这种写法在资源充足的 PC 上没问题但在一些配置不高的工控机上会白白消耗 CPU 和 GPU。我后来改成“脏检查 按需渲染”模式。定义几个触发重绘的条件相机位置变化、数据刷新、动画播放中。只有这些情况发生时才调用 renderer.render()。实测下来空闲状态下 GPU 占用率从 70% 降到 10% 以下这个优化对大屏常驻场景特别有效。6. 坐标、拾取、字体接入真实业务时的几个经典坑6.1 坐标单位与 CAD 图纸的扭曲问题这个坑几乎每个做三维仓库的人都会踩。CAD 图纸的坐标单位通常是毫米而 Three.js 世界坐标里1 个单位默认可视作 1 米。如果你直接把 CAD 的坐标搬进场景模型会大一千倍相机进到模型内部啥也看不见。转换公式很简单function cadToWorld(cadPoint) { return { x: cadPoint.x / 1000, y: cadPoint.z / 1000, z: cadPoint.y / 1000 }; }但更麻烦的是坐标轴的差异。CAD 平面图是 X/Y 平面Z 表示高度Three.js 是 X/Z 为地面平面Y 表示高度。所以映射的时候要把 CAD 的 Y 变成 Three.js 的 ZCAD 的 Z 变成 Three.js 的 Y。方向搞反了仓库就会竖起来。我遇到过一次诡异的现象地图导入后货架位置斜了排查了半天发现是 CAD 里有一个旋转组件的基点没归零。建议导入数据之前先写一个校验函数把已知点的坐标打在页面上和图纸原始坐标比对能省很多排错时间。6.2 z-fighting 闪烁问题三维仓库的场景里有很多互相贴得很近的面比如库位小盒放在层板上如果高度差只有 0.001相机的深度缓冲精度不够就会产生闪烁。常见解决办法有三个一是拉开最小间距把库位小盒放在层板表面上方 0.01 到 0.02 的高度。二是给同一位置的面设置 polygonOffsetmaterial.polygonOffset true; material.polygonOffsetFactor 1; material.polygonOffsetUnits 1;三是调整相机的 near 和 far 值。这个容易被忽略。透视相机的深度精度分布是近密远疏near 设得太小、far 设得太大远处的深度精度就很差。仓库场景里near 设 0.1、far 设 1000 基本是足够的如果仓库特别大再适当调整。6.3 拾取穿透与误选问题Raycaster 射线从鼠标位置出发会穿过前面的物体一路往后命中所有相交物体。如果你不加筛选取第一个结果可能命中的是货架后面的另一个库位或者货架立柱本身。我的处理方式是给所有可拾取物体打上 userData.type 标记slotMesh.userData { type: slot, slotInfo: { ... } };然后过滤命中列表const hit intersects.find((item) item.object.userData.type slot);另外当货架密度很大时射线从很斜的角度点下去可能命中旁边的库位而不是正下方的库位。这需要结合鼠标点击位置和相机视角判断命中角度。实在不行就增加一个“命中优先范围”只取射线距离在物体包围盒半径范围内的结果。6.4 Canvas 文字贴图模糊问题给库位标签或者货架区域编号做文字标注时最省事的办法是用 Canvas 画一张文字纹理贴到平面模型上。但默认画出来的字在大屏上糊成一片。原因很简单Canvas 的分辨率不够文字纹理被 GPU 拉伸了。解决办法是创建 Canvas 时就把尺寸放大两倍并且在绘制时把字号也放大两倍const canvas document.createElement(canvas); canvas.width 256; canvas.height 64; const ctx canvas.getContext(2d); ctx.font bold 32px sans-serif;然后生成纹理时设置好 minFilter 和 magFilterconst texture new THREE.CanvasTexture(canvas); texture.minFilter THREE.LinearFilter; texture.needsUpdate true;如果标注数量很多而且需要实时更新我建议考虑 CSS2DRenderer把 HTML 标签覆盖在三维场景上。这样字体清晰、样式好调也不会引入 Canvas 文字贴图的那些坑。唯一要注意的是CSS2DRenderer 标签会跟随相机位置变化需要处理好层级遮挡关系。7. 源码包跑通与二次开发建议7.1 从零跑到看到效果拿到“基于 Three.js 的仓库可视化管理系统源码.zip”第一件事是在本地把项目跑起来。解压之后确认已经装好 Node.js 16 以上版本然后在项目根目录执行npm install npm run dev等 Vite 启动浏览器打开终端提示的本地地址就能看到默认仓库的三维场景了。如果接口还没有真实数据系统会默认走 mock 数据。mock 数据放在 public/data 目录下包含仓库布局和库存快照你可以直接改 JSON 文件刷新页面看效果。生产环境打包用npm run build产物在 dist 目录静态部署到 Nginx 或者对象存储上都行。打包之前记得检查 .env 文件里的接口地址配置别把后端测试地址带到生产环境。7.2 接入真实 WMS 的接口规范建议源码包里默认对接的接口设计我在这里给一个参考规范方便你让后端按这个格式返回数据。库存快照接口建议返回如下结构{ code: 0, data: { stock: [ { rackId: A-03, level: 2, slot: 4, status: occupied, sku: SKU10086, quantity: 120, updatedAt: 2025-01-12 10:30:00 } ], devices: [ { id: AGV-001, status: running, position: [8.5, 0, 61.2], heading: 90 } ] } }实时更新建议用 WebSocket 推送增量数据推送频率不用太高每 1 到 2 秒一次足够。数据格式里尽量带上 rackId、level、slot 这样精确定位库位的字段否则渲染层不知道该更新哪个实例。7.3 二次开发路线从改配置到加功能以我的经验拿到这套源码做二次开发建议按这个顺序来。第一步改仓库布局。打开 public/data/layout.json对照自己仓库的平面尺寸调整区域位置、货架行列层数。这一步不需要碰任何 Three.js 代码。第二步替换设备模型。把 public/models 里的 agv.glb、fork.glb 替换成实际项目需要的 3D 模型注意 GLTF/GLB 格式。如果模型导出时带了很多材质加载前尽量压缩一下纹理。第三步接真实接口。修改 src/loaders/dataService.js把 fetchStockSnapshot 和 subscribeStockUpdate 换成调真实后端接口mock 数据的开关在 config 里有个配置项。第四步新增业务功能。比如货架超期预警、库位热度分析、出入库轨迹回放。这些功能在业务层扩展状态对象在渲染层加对应的表现即可。现有分层结构下新增功能基本不会影响原有的场景渲染。做完这几步一个能交付的三维仓库可视化管理系统就出来了。我个人在实际项目里感受最深的一点是这类系统前期搭建不难难的永远是数据准确性和长期维护成本。建议在代码里保留好日志和状态校验的钩子每次数据更新打印关键变更点排查问题的时候会非常管用。如果你也在做类似项目欢迎交换经验特别是在大仓库性能或者异构数据接入方面每家遇到的情况都不太一样。本文还有配套的精品资源点击获取