1. 从一张空白画布到可巡检园区:这个项目到底在做什么
去年秋天,一个做农业园区的朋友找到我,说他们新拿了一块地,想做一个"能看、能点、能走进去"的智慧农业大屏。不是那种PPT里放几张图表、几个数字在跳的假大屏,而是真正能把整个园区——大棚、水肥一体机、气象站、虫情灯、巡检机器人——用三维方式还原出来,鼠标能转、能缩放、能点开每个设备看实时数据的那种。
这个需求听起来不复杂,但真正动手之后我发现,它踩中了三个非常现实的痛点。第一,传统三维建模太慢,一个中等规模的农业园区,光是建模加贴图,外包报价动辄几万块,周期三周起步,改一版又要等一周。第二,农业场景的模型和城市、工业场景完全不一样,大棚的拱形结构、连栋温室的玻璃反光、田垄的纹理,这些在通用模型库里几乎找不到现成的。第三,大屏不是做完就完事,它要接实时数据,要能巡检,要能随着季节变化调整作物状态,这就要求整个流程必须可迭代、可维护。
我最终采用的方案是:用 GPT-6 Astra 做需求拆解、代码生成和逻辑编排,用 Tripo3D 做三维模型的快速生成,用 Three.js 做前端渲染和交互,中间用 Blender MCP 做模型格式的转换和优化,最终输出 GLB 格式的模型资产。整套流程跑下来,从调研到可巡检的园区大屏,我一个人用了大约两周时间,其中真正写代码的时间不到五天,大部分时间花在模型调整和场景布局上。
这篇文章我会把这套流程完整拆开,包括每个环节为什么这么选、具体怎么操作、我踩过哪些坑、哪些地方可以抄作业、哪些地方你必须根据自己的场景调整。适合有一定前端基础、想快速上手三维可视化大屏的开发者,也适合做智慧农业、园区管理、数字孪生相关产品的同行参考。即使你之前没碰过 Three.js,跟着走一遍也能跑通。
2. 为什么是 GPT-6 Astra + Tripo3D + Three.js 这套组合
2.1 传统三维大屏开发流程的瓶颈在哪里
先说清楚传统做法为什么慢。一个标准的智慧农业三维大屏,传统流程大致是这样的:需求方给平面图,建模师用 3ds Max 或 Blender 建场景,贴图师做材质,然后导出 FBX 或 OBJ,前端用 Three.js 加载,再写交互逻辑。这个链条里,建模和贴图占了整个项目 60% 以上的时间,而且一旦需求变更——比如大棚数量从 8 个变成 12 个,或者作物从水稻换成草莓——模型就要重做。
更麻烦的是沟通成本。建模师不懂农业,不知道连栋温室和日光温室的区别,不知道水肥一体机长什么样,做出来的模型往往"形似神不似"。前端也不懂模型,拿到一个几十万面的 FBX,加载卡顿、贴图丢失、坐标错位,排查起来非常痛苦。
我试过用传统的模型库方案,下载了一堆免费的农业模型,结果发现要么精度太低像玩具,要么风格不统一,有的写实有的卡通,拼在一起非常违和。而且免费模型大多没有版权保障,商用有风险。
2.2 Tripo3D 在农业场景建模中的实际表现
Tripo3D 的核心能力是"图生模型"和"文生模型",你给它一张参考图或者一段描述,它能在几分钟内生成一个带贴图的三维模型,输出格式支持 GLB、FBX、OBJ 等。我实测下来,它在农业场景里的表现有几个明显特点。
对于规则几何体为主的设备,比如水肥一体机、气象站支架、虫情测报灯,Tripo3D 的生成质量相当高,基本一次成型,面数控制在合理范围,贴图也过得去。我生成一个水肥一体机大概用了 3 分钟,面数在 8000 左右,直接扔进 Three.js 加载毫无压力。
对于有机形态的东西,比如作物、树木、地形,Tripo3D 的表现就参差不齐了。我试过生成"一垄草莓",出来的模型远看还行,近看叶片和果实的细节比较糊。后来我的做法是:作物类模型用 Tripo3D 生成基础形态,然后在 Blender 里做减面和贴图替换,或者直接用实例化的方式在 Three.js 里批量复制。
还有一个关键点:Tripo3D 生成的模型默认是独立坐标系,多个模型拼在一起时,原点和朝向经常对不上。我的经验是,生成时尽量让模型朝向统一(比如都朝 +Z),然后在 Blender 里统一调整原点到几何中心,再导出 GLB。这一步不做,后面布局会非常痛苦。
2.3 GPT-6 Astra 在整个链路里承担什么角色
GPT-6 Astra 在这个项目里不是用来"生成模型"的,它是整个流程的"调度中枢"和"代码工厂"。具体来说,它帮我做了四件事。
第一,需求拆解。我把园区的平面图、设备清单、数据字段扔给它,让它帮我梳理出三维场景需要哪些模型、哪些交互、哪些数据接口。它输出的结构化清单,比我手动整理快得多,而且不容易漏。
第二,代码生成。Three.js 的场景初始化、相机控制、光照配置、模型加载、射线拾取、数据面板绑定,这些代码大部分是 GPT-6 Astra 生成的。我只需要告诉它"我要一个带轨道控制的场景,点击模型弹出信息面板,面板数据从 WebSocket 来",它就能给出可运行的代码框架。
第三,问题排查。Three.js 贴图不显示、模型加载后位置偏移、光照导致材质发黑,这些问题我遇到时直接把报错和代码片段扔给它,它能快速定位原因。比如贴图不显示,它提醒我检查texture.colorSpace和renderer.outputColorSpace是否匹配,这个坑我后面会详细讲。
第四,逻辑编排。巡检路径怎么走、设备状态怎么映射到颜色、季节变化怎么切换作物模型,这些业务逻辑用自然语言描述给 GPT-6 Astra,它能帮我转成清晰的代码结构。
2.4 Blender MCP 在模型流转中的桥梁作用
Blender MCP 是一个让 AI 能直接操作 Blender 的协议层工具。在这个项目里,它的价值在于"批量处理"。Tripo3D 生成的模型往往需要统一缩放、统一原点、减面、合并材质,如果手动一个个处理,几十个模型能把你逼疯。通过 Blender MCP,我可以用自然语言指令让 Blender 批量执行这些操作,比如"把所有 GLB 模型的原点移到几何中心,缩放统一为 1:1 米制单位,面数超过 2 万的做减面处理"。
不过要说明的是,Blender MCP 目前还不是完全稳定,复杂指令偶尔会执行失败,我的做法是把大指令拆成小指令,一步步执行,每步确认结果。这个工具适合有一定 Blender 基础的人,完全新手可能会被它的报错搞懵。
3. 环境搭建与工具链配置的完整步骤
3.1 前端项目初始化与 Three.js 版本选择
Three.js 的版本迭代很快,不同版本之间 API 差异不小。我建议用当前稳定的 r160 以上版本,通过 npm 安装而不是直接引 CDN,这样方便配合构建工具做代码分割和资源压缩。
初始化项目我用的是 Vite,原因是启动快、热更新稳、对 ES Module 支持好。具体命令如下:
npm create vite@latest agri-dashboard -- --template vanilla cd agri-dashboard npm install three npm install three/examples/jsm/controls/OrbitControls这里有个细节:OrbitControls 在新版 Three.js 里已经从核心包移到了 examples/jsm 目录,导入路径是three/examples/jsm/controls/OrbitControls.js,不要再按老教程写three/examples/js/controls/OrbitControls.js,否则会报模块找不到。
项目结构我建议这样组织:
src/ main.js // 入口,场景初始化 scene.js // 场景、相机、光照、渲染器 loader.js // 模型加载与缓存 interact.js // 射线拾取与交互 data.js // 数据接口与状态管理 models/ // GLB 模型资产 textures/ // 贴图资产这样拆分的好处是,后面接实时数据、加巡检逻辑时,不会把所有代码堆在一个文件里。
3.2 Tripo3D 模型生成的参数设置与导出规范
Tripo3D 生成模型时,有几个参数直接影响后续使用。我在反复试错后总结出一套适合农业场景的配置。
| 参数项 | 推荐设置 | 原因说明 |
|---|---|---|
| 输出格式 | GLB | 单文件包含模型和贴图,Three.js 原生支持 |
| 面数上限 | 15000 面以内 | 超过后大屏加载会卡,农业设备不需要过高精度 |
| 贴图分辨率 | 1024x1024 | 2048 在大屏上肉眼差别不大,但体积翻倍 |
| 模型朝向 | 统一 +Z 朝前 | 避免布局时反复旋转 |
| 原点位置 | 几何中心 | 方便后续定位和缩放 |
生成时的提示词也很关键。不要只写"水肥一体机",要写"农业水肥一体机,白色金属外壳,带控制面板和管道接口,写实风格,正面视角"。描述越具体,生成结果越可控。我试过只写设备名,出来的模型经常缺胳膊少腿,加上材质、颜色、视角描述后,成功率明显提升。
导出 GLB 后,我建议先在 https://gltf-viewer.donmccurdy.com/ 这类在线查看器里过一遍,确认模型完整、贴图正常、没有多余的空节点。有些模型会带一堆无用的空对象,加载到 Three.js 里会增加遍历开销,最好在 Blender 里清理掉。
3.3 Blender MCP 的接入与批量处理脚本
Blender MCP 的接入需要先安装 Blender(建议 4.0 以上版本),然后配置 MCP 服务端。具体步骤我不展开讲安装细节,重点说批量处理的思路。
我处理的模型大概有 40 多个,包括大棚、设备、树木、道路、围栏等。手动处理的话,每个模型打开、调整、导出,至少 5 分钟,40 个就是 3 个多小时。用 Blender MCP 批量处理后,整个过程压缩到 20 分钟左右。
批量处理的核心指令逻辑是这样的:
# 伪代码示意,实际通过 MCP 指令触发 for model in glb_files: import_glb(model) set_origin_to_geometry_center() scale_to_unit_meter() if face_count > 20000: decimate(ratio=0.5) merge_materials() export_glb(model.replace('.glb', '_opt.glb'))这里有个坑要注意:减面操作会破坏 UV 映射,导致贴图错乱。所以减面比例不要超过 0.5,而且减面后一定要检查贴图。我的做法是,面数在 15000 以内的模型不减面,只做原点和缩放处理;超过 20000 面的才减面,减完人工抽查几个。
3.4 模型资产的目录规范与加载策略
模型资产的组织方式直接影响加载性能。我的做法是按场景区域分目录:
models/ greenhouse/ // 大棚相关 equipment/ // 设备相关 terrain/ // 地形道路 vegetation/ // 植被 common/ // 通用组件加载策略上,我用的是"按需加载 + 预加载关键模型"。首屏只加载地形和主要建筑,设备模型在用户点击某个区域时再加载。Three.js 的 GLTFLoader 支持异步加载,配合 LoadingManager 可以做加载进度条。
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { LoadingManager } from 'three'; const manager = new LoadingManager(); manager.onProgress = (url, loaded, total) => { console.log(`加载进度: ${loaded}/${total}`); }; const loader = new GLTFLoader(manager); loader.load('models/greenhouse/gh_01.glb', (gltf) => { scene.add(gltf.scene); });预加载的关键模型,我会在页面初始化时用loader.loadAsync并行加载,加载完成后缓存到 Map 里,后续复用直接取,避免重复请求。
4. Three.js 场景搭建中那些文档不会告诉你的细节
4.1 贴图不显示:从 colorSpace 到路径的完整排查链路
贴图不显示是 Three.js 新手最容易遇到的问题,我一开始也踩了这个坑。模型加载出来了,但表面是灰的,贴图完全没生效。我排查了整整一个下午,最后发现是色彩空间的问题。
Three.js 从 r152 开始,默认使用 sRGB 色彩空间,但贴图需要手动设置colorSpace。如果贴图是颜色贴图(map),要设置为THREE.SRGBColorSpace;如果是法线贴图、粗糙度贴图等数据贴图,保持默认的线性空间即可。
const texture = new THREE.TextureLoader().load('textures/ground.jpg'); texture.colorSpace = THREE.SRGBColorSpace; // 颜色贴图必须设置同时,渲染器的输出色彩空间也要匹配:
renderer.outputColorSpace = THREE.SRGBColorSpace;这两个设置不匹配,贴图就会发灰或者过曝。除了色彩空间,贴图不显示还有几个常见原因:路径错误(GLB 内嵌贴图不会出现这个问题,外链贴图才会)、跨域限制(本地开发用 Vite 代理或把贴图放同域)、材质被覆盖(加载后手动改了 material 导致贴图丢失)。我的排查顺序是:先看控制台有没有 404,再看 colorSpace,最后看材质是否被覆盖。
4.2 光照配置:为什么你的农业大屏看起来像灵堂
农业场景的光照和工业场景完全不同。工业场景可以用冷色调、高对比度,显得科技感强;但农业场景如果光照没调好,整个画面会发青发灰,看起来像灵堂,非常压抑。
我的光照方案是"半球光 + 平行光 + 环境光"三层组合。半球光提供天空和地面的基础照明,平行光模拟太阳产生阴影,环境光补充暗部细节。
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 1.2); scene.add(hemiLight); const dirLight = new THREE.DirectionalLight(0xfff5e6, 2.0); dirLight.position.set(50, 80, 30); dirLight.castShadow = true; dirLight.shadow.mapSize.set(2048, 2048); scene.add(dirLight); const ambientLight = new THREE.AmbientLight(0xffffff, 0.4); scene.add(ambientLight);平行光的颜色我用了偏暖的0xfff5e6,模拟午后阳光,这样大棚和作物的颜色会更自然。阴影贴图分辨率设 2048 是性能和质量的平衡点,再高会明显影响帧率。
还有一个细节:农业场景的地面不要用纯色,要用带纹理的贴图,否则光照打上去会显得很假。我用 Tripo3D 生成了一块土地纹理,平铺到地面上,配合光照后立体感明显提升。
4.3 相机控制与视角预设:让非技术用户也能操作
大屏的使用者往往不是技术人员,可能是园区管理员或者领导。如果相机控制太复杂,他们根本不会用。我的做法是提供"自由视角 + 预设视角"两套控制。
自由视角用 OrbitControls,限制旋转角度和缩放范围,避免用户转到地底下或者缩到模型内部:
const controls = new OrbitControls(camera, renderer.domElement); controls.maxPolarAngle = Math.PI / 2.2; // 限制不能转到地面以下 controls.minDistance = 20; controls.maxDistance = 200; controls.enableDamping = true; controls.dampingFactor = 0.05;预设视角我设置了四个:全景俯瞰、大棚特写、设备区域、巡检路线。用户点击按钮,相机用缓动动画过渡到对应位置。缓动我用的是简单的线性插值:
function flyTo(targetPos, targetLookAt, duration = 1000) { const startPos = camera.position.clone(); const startTime = performance.now(); function animate() { const elapsed = performance.now() - startTime; const t = Math.min(elapsed / duration, 1); const ease = t * (2 - t); // 缓出效果 camera.position.lerpVectors(startPos, targetPos, ease); camera.lookAt(targetLookAt); if (t < 1) requestAnimationFrame(animate); } animate(); }这个交互实测下来,非技术用户上手很快,基本不需要培训。
4.4 模型拾取与信息面板绑定:点击设备弹出实时数据
巡检功能的核心是"点击模型,弹出该设备的信息面板"。Three.js 用 Raycaster 实现拾取:
const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); renderer.domElement.addEventListener('click', (event) => { const rect = renderer.domElement.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(clickableObjects, true); if (intersects.length > 0) { const obj = intersects[0].object; const deviceId = findDeviceId(obj); showInfoPanel(deviceId); } });这里有个性能坑:如果场景里模型很多,每次点击都遍历所有对象,会卡。我的做法是维护一个clickableObjects数组,只把需要交互的设备放进去,地形、道路、植被不参与拾取。另外,intersectObjects的第二个参数设为true表示递归检测子对象,因为 GLB 模型通常是层级结构,点击到的往往是子网格。
信息面板我用的是 HTML 覆盖层,而不是 Three.js 的 Sprite 或 CSS2DRenderer。原因是 HTML 面板做样式、绑数据、加图表都更方便,而且不受三维场景光照影响。面板定位用屏幕坐标,跟随点击位置或者固定在侧边栏。
5. 从模型到可巡检:业务逻辑的落地过程
5.1 设备状态映射:用颜色和动画表达实时数据
大屏的价值在于"一眼看出哪里有问题"。我把设备状态映射成了颜色和动画:正常是绿色,预警是黄色,故障是红色,离线是灰色。颜色通过修改材质的emissive实现,这样在光照下也能看清。
function setDeviceStatus(mesh, status) { const colorMap = { normal: 0x00ff00, warning: 0xffff00, error: 0xff0000, offline: 0x888888 }; mesh.traverse((child) => { if (child.isMesh) { child.material.emissive = new THREE.Color(colorMap[status]); child.material.emissiveIntensity = 0.5; } }); }故障设备我还会加一个脉冲动画,让emissiveIntensity在 0.3 到 0.8 之间循环,这样在满屏绿色里一眼就能看到红色在闪。
数据来源我用的是 WebSocket,后端推送设备状态变更,前端收到后更新对应模型的颜色。这里要注意,WebSocket 消息可能很频繁,不要每来一条就遍历场景,而是维护一个设备 ID 到模型对象的 Map,直接定位更新。
5.2 巡检路线设计:让相机沿着路径自动巡航
巡检模式是我个人觉得最实用的功能。点击"开始巡检",相机会沿着预设路径自动移动,每到一处设备就停留几秒,弹出该设备的信息,然后继续前进。
路径我用 CatmullRomCurve3 定义,给几个关键点,Three.js 会自动生成平滑曲线:
const curve = new THREE.CatmullRomCurve3([ new THREE.Vector3(-50, 30, 50), new THREE.Vector3(0, 25, 30), new THREE.Vector3(50, 30, 50), new THREE.Vector3(50, 35, -50), new THREE.Vector3(-50, 35, -50) ]); const points = curve.getPoints(200); let progress = 0; function patrol() { progress += 0.001; if (progress > 1) progress = 0; const pos = curve.getPointAt(progress); camera.position.copy(pos); camera.lookAt(0, 0, 0); requestAnimationFrame(patrol); }停留点的处理,我是在路径上标记几个关键位置,当相机经过时触发信息面板。判断"经过"用的是距离阈值,相机位置和停留点距离小于某个值时触发。
实测下来,巡检一圈大概 40 秒,节奏刚好,不会太快看不清,也不会太慢让人等得不耐烦。
5.3 季节与作物状态切换:一套模型适配四季
农业园区的作物是随季节变化的,如果大屏永远显示同一种状态,会显得很假。我的做法是准备几套作物模型(幼苗、生长期、成熟期、收割后),根据当前日期自动切换,也支持手动切换。
function switchCropSeason(season) { cropGroup.clear(); const modelPath = `models/vegetation/crop_${season}.glb`; loader.load(modelPath, (gltf) => { cropGroup.add(gltf.scene); }); }这里有个性能考虑:如果每次切换都重新加载模型,会有明显卡顿。我的做法是预加载所有季节的模型,缓存在内存里,切换时只是显示和隐藏,不重新加载。内存占用会增加,但切换体验流畅很多。
5.4 数据面板与三维场景的联动逻辑
信息面板不是孤立的,它要和三维场景联动。比如面板里点击"定位"按钮,相机要飞到该设备;面板里显示设备列表,点击某一项,场景里对应设备要高亮。
高亮我用的是描边效果,通过修改材质的emissive或者加一层 OutlinePass。OutlinePass 效果更好但性能开销大,设备多的时候会卡。我最后用的是简单的 emissive 增强,配合缩放动画,效果够用且性能友好。
function highlightDevice(mesh) { mesh.traverse((child) => { if (child.isMesh) { child.material.emissive = new THREE.Color(0x00aaff); child.material.emissiveIntensity = 0.8; } }); // 3 秒后恢复 setTimeout(() => setDeviceStatus(mesh, 'normal'), 3000); }面板和场景的通信我用的是事件总线模式,面板触发事件,场景监听并响应,避免直接耦合。
6. 性能优化与实测踩坑记录
6.1 模型面数与加载时间的实测数据
我实测了不同面数模型的加载时间,数据如下:
| 模型面数 | 文件大小 | 加载时间(本地) | 加载时间(网络) |
|---|---|---|---|
| 5000 | 1.2MB | 80ms | 400ms |
| 15000 | 3.5MB | 200ms | 1.2s |
| 30000 | 7.8MB | 450ms | 2.8s |
| 60000 | 15MB | 900ms | 5.5s |
从数据看,单个模型面数控制在 15000 以内,网络加载时间在 1.2 秒左右,是可以接受的。超过 30000 面,加载时间明显上升,而且渲染帧率也会下降。我的建议是,农业场景的设备模型,15000 面足够表现细节了,没必要追求高精度。
6.2 实例化渲染:让几百棵树不拖垮帧率
园区里如果有几百棵树,每棵都单独加载模型,帧率会崩。这时候要用 InstancedMesh,把同一个模型实例化渲染。
const treeGeometry = await loadGeometry('models/vegetation/tree.glb'); const treeMaterial = await loadMaterial('models/vegetation/tree.glb'); const treeCount = 200; const instancedTrees = new THREE.InstancedMesh(treeGeometry, treeMaterial, treeCount); const dummy = new THREE.Object3D(); for (let i = 0; i < treeCount; i++) { dummy.position.set( (Math.random() - 0.5) * 200, 0, (Math.random() - 0.5) * 200 ); dummy.rotation.y = Math.random() * Math.PI * 2; dummy.scale.setScalar(0.8 + Math.random() * 0.4); dummy.updateMatrix(); instancedTrees.setMatrixAt(i, dummy.matrix); } scene.add(instancedTrees);用实例化后,200 棵树的渲染开销和 1 棵树差不多,帧率从 20 帧提升到 60 帧。这是 Three.js 性能优化里性价比最高的一招。
6.3 贴图压缩与格式选择:KTX2 还是 WebP
贴图是模型体积的大头。Tripo3D 生成的贴图默认是 PNG,1024x1024 的 PNG 大概 1-2MB。如果模型多,贴图体积会爆炸。
我的做法是转成 WebP,体积能压缩 60% 左右,而且 Three.js 支持良好。如果追求极致,可以用 KTX2 格式,GPU 直接解压,加载更快,但需要额外的转码工具和加载器配置,复杂度较高。
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js'; const ktx2Loader = new KTX2Loader(); ktx2Loader.setTranscoderPath('path/to/basis/'); ktx2Loader.detectSupport(renderer);对于大多数农业大屏项目,WebP 足够用了,KTX2 适合模型特别多、对加载速度要求极高的场景。
6.4 那些让我熬夜的报错与最终解法
有几个报错我印象特别深,记录一下。
第一个是THREE.GLTFLoader: Unknown extension "KHR_materials_pbrSpecularGlossiness"。这是因为 Tripo3D 导出的 GLB 用了旧的材质扩展,新版 Three.js 不再支持。解法是在 Blender 里重新导出,选择 Metallic Roughness 材质,或者用 gltf-transform 工具转换。
第二个是模型加载后位置偏移。原因是 GLB 内部有变换矩阵,加载后gltf.scene的 position 不是原点。解法是加载后手动重置:
gltf.scene.position.set(0, 0, 0); gltf.scene.rotation.set(0, 0, 0); gltf.scene.scale.set(1, 1, 1);第三个是内存泄漏。反复加载和卸载模型,内存持续增长。原因是 Three.js 的几何体和材质不会自动释放,需要手动 dispose:
function disposeModel(model) { model.traverse((child) => { if (child.isMesh) { child.geometry.dispose(); if (Array.isArray(child.material)) { child.material.forEach(m => m.dispose()); } else { child.material.dispose(); } } }); }这个坑不踩一次很难记住,但一旦遇到,排查起来很费时间。
7. 这套流程的边界与后续可扩展方向
7.1 哪些场景适合这套方案,哪些不适合
这套方案最适合的是"中等精度、快速迭代、预算有限"的农业园区大屏。如果你的需求是电影级渲染、毫米级精度、或者需要复杂的物理模拟,那 Tripo3D 生成的模型精度不够,还是得走传统建模路线。
另外,如果园区面积特别大(比如几千亩),模型数量上千,那加载和渲染压力会很大,需要考虑分块加载、LOD 分级等更复杂的优化手段。我这套方案在 200 亩以内的园区实测流畅,再大就要做针对性优化了。
7.2 从单机大屏到多人协同的演进思路
目前这套大屏是单机运行的,如果要做多人协同——比如多个管理员同时查看、标注、下发指令——需要引入后端状态同步。我的思路是用 WebSocket 做实时通信,用操作日志做冲突解决,用房间概念隔离不同园区的数据。
这部分我还没完整实现,但架构上已经预留了接口。数据层和渲染层是分离的,接入协同逻辑时不需要改动渲染代码。
7.3 模型资产的复用与版本管理经验
Tripo3D 生成的模型是可以复用的。我建了一个模型库,按设备类型分类,每个模型记录生成参数、面数、贴图信息。下次做新项目时,相似的设备直接复用,只需要调整颜色和尺寸。
版本管理我用的是 Git LFS,GLB 文件走 LFS 存储,避免仓库体积过大。每次模型更新,commit message 里写清楚改了什么,方便回溯。
这套流程跑通之后,我做第二个类似项目时,从调研到上线只用了五天。模型复用率大概 60%,代码复用率 80%。这就是把流程标准化的价值——第一次慢,后面越来越快。
最后分享一个我在实际操作中的体会:三维大屏项目,技术只占一半,另一半是对业务场景的理解。农业园区的设备怎么摆、作物怎么长、巡检路线怎么设计,这些不是技术问题,是业务问题。多去现场看看,多和园区的人聊聊,比闷头写代码重要得多。我第一版做出来的大屏,设备位置全是错的,因为我是照着平面图摆的,而平面图和实际布局有偏差。后来去现场转了一圈,回来调整后,园区的人一看就说"对,就是这个样子"。