1. 项目缘起与整体设计思路
1.1 为什么想到用 AI 做智慧农业 3D 大屏
去年底我接手了一个农业园区的数字化改造项目,甲方是本地一个两千多亩的现代农业示范园,种的是设施蔬菜和部分露天果园。他们原本有一套传统的 Web 管理后台,表格加图表那种,数据倒是全,但园区主任跟我说了一句特别实在的话:“这玩意儿给领导看还行,给技术员用根本没人点,大家还是靠腿跑。”
这句话点醒了我。农业园区的核心痛点不是数据不够,而是数据和人、和地块、和设备之间的空间关系断了。温度异常你知道是几号棚,但几号棚在园区哪个位置、旁边挨着哪个泵房、巡检路线怎么走最省时间,这些信息在二维表格里是割裂的。所以我的核心思路很明确:做一个能“走进去”的 3D 园区大屏,把传感器数据、设备状态、巡检任务全部挂到三维空间上。
技术选型上我定了三条线:建模用 Tripo3D 配合 Blender 做资产,前端用 Three.js 做渲染和交互,AI 部分用 GPT-6 Astra 做数据理解和巡检决策。这个组合不是拍脑袋定的,后面我会详细拆解每个选择的理由。
1.2 整体架构分层拆解
整个系统我分成了四层,从下往上说:
数据层负责对接园区现有的物联网设备。这块不用重造轮子,园区已经有土壤墒情传感器、气象站、水肥一体机控制器,走的是标准 Modbus 和 MQTT 协议。我写了一个 Node.js 的网关服务,把 MQTT 消息转成 WebSocket 推给前端,同时落一份时序数据到本地数据库。
模型层是这次的重点。园区有 32 栋日光温室、4 座连栋大棚、1 个控制中心、若干泵房和道路。全部用人工建模不现实,工期不允许。我的方案是:标准化的温室用参数化建模批量生成,异形建筑和关键设备用 Tripo3D 生成基础网格再进 Blender 精修。
渲染层用 Three.js。选它而不是 Babylon.js 或者 Unity WebGL,核心原因是包体积和加载速度。农业大屏经常要在园区的老旧电脑甚至平板上打开,Unity WebGL 动辄几十兆的 wasm 包直接劝退。Three.js 配合 GLB 格式,整个场景压缩后控制在 8MB 以内,首屏三秒能出来。
智能层用 GPT-6 Astra。它在这里不是拿来聊天的,而是做两件实事:一是把自然语言的巡检指令转成结构化的任务参数,二是根据多传感器数据做异常归因分析。比如技术员输入“帮我看看三号区最近有没有问题”,Astra 要能理解“三号区”对应哪些棚、调取哪些数据、按什么逻辑判断。
1.3 关键选型背后的取舍逻辑
先说 Tripo3D。市面上 AI 生成 3D 模型的工具不少,我实测过几个,Tripo3D 在建筑类规则几何体上的表现明显更稳。温室这种有大量重复结构(拱架、立柱、棚膜)的模型,它生成的拓扑比较干净,面数控制得住。腾讯那边和 Tripo3D 的合作我也关注了,主要是云渲染和资产管线的整合,对个人开发者来说暂时用不上,但说明这个方向是被大厂认可的。
再说 GPT-6 Astra。它相比前代最大的提升是长上下文下的结构化输出稳定性。我要它从一段包含十几个传感器读数的描述里抽出异常项并给出处置建议,输出必须是严格的 JSON,字段不能错。实测下来 Astra 在这个任务上的格式遵循率比上一代高出一大截,基本不用做后处理校验。
Blender MCP 是这次的一个惊喜。它让我可以在 Blender 里通过自然语言指令批量处理模型,比如“把所有温室的棚膜材质换成半透明”“给这批模型统一减面到 5000 面以下”。32 栋温室的模型处理,手动做要一整天,用 MCP 脚本半小时搞定。
2. 核心资产生产:从 Tripo3D 到 GLB 的完整管线
2.1 Tripo3D 生成模型的实际操作与参数调优
Tripo3D 的输入方式有两种:文本描述和参考图。我的经验是建筑类模型一定要用参考图,纯文本生成的几何结构经常跑偏。具体做法是先用 CAD 图纸导出一张白底黑线的正视图,或者直接拿园区航拍图裁出单栋温室的俯视图,作为参考图喂进去。
生成参数这块有几个关键项。面数控制我一般设在 8000 到 15000 之间,太低细节丢失,太高后面减面麻烦。拓扑类型选四边形优先,这样进 Blender 后做细分或者变形都方便。对称性对于温室这种左右对称的结构要打开,能显著提升生成质量。
生成出来的模型有个通病:底面不平。AI 不理解“建筑要坐在地上”这件事,经常生成一个微微倾斜或者底部有凸起的网格。我的处理办法是在 Blender 里加一个平面,用布尔运算切平底面,或者直接用对齐工具把最低点拉到 Z=0。
还有一个坑是法线方向。Tripo3D 生成的模型偶尔会有法线翻转的面,在 Three.js 里表现为某些面是黑的。进 Blender 后按 Shift+N 统一法线方向,基本能解决。
2.2 Blender MCP 批量处理与减面策略
Blender MCP 的核心价值是把重复劳动脚本化。我装好 MCP 插件后,在 Blender 里打开一个对话面板,直接用中文下指令。比如:
把这批选中的模型统一执行以下操作: 1. 应用所有变换 2. 合并重叠顶点,阈值 0.001 3. 添加 Decimate 修改器,比例 0.5 4. 应用修改器 5. 导出为 GLB,文件名用对象名这段指令它会自动转成 Python 脚本执行。32 栋温室模型,从导入到导出 GLB,全程不到二十分钟。
减面这块我要多说一句。不是面数越低越好。农业大屏的观看距离通常在三到五米,太低的模型边缘会有明显锯齿。我的经验值是:主体建筑控制在 3000 到 5000 面,精细设备(比如水肥一体机)可以到 8000 面,远景的山体树木用 500 面以下的简模就行。整个场景总面数控制在 50 万以内,中端显卡能稳定 60 帧。
2.3 GLB 格式的优化与贴图处理
GLB 是 Three.js 最友好的格式,但直接导出的 GLB 往往体积偏大。我的优化流程是这样的:
贴图压缩是重头。Blender 导出的贴图默认是 PNG,一张 2048 的贴图就好几兆。我用工具转成 WebP 格式,质量设 80%,体积能降到原来的三分之一。如果场景里贴图多,还可以打成 KTX2 压缩纹理,Three.js 有对应的 loader 支持。
材质合并也很关键。同一个 GLB 里如果有多个材质用同一张贴图,Three.js 会重复加载。我在 Blender 里就把共用贴图的材质合并成一个,减少 draw call。
LOD 分级是可选项但强烈建议。给每栋温室做两到三个细节层级,相机拉远时自动切换到低模。Three.js 的 LOD 对象用起来很简单,但效果立竿见影,帧率能提升百分之三四十。
注意:GLB 导出时一定要勾选“应用修改器”和“+Y 向上”。Three.js 默认是 Y 轴向上,Blender 是 Z 轴向上,不转换的话模型会躺在地上。
3. Three.js 场景搭建与交互实现
3.1 快速创建 Three.js 项目的工程化配置
我不建议用官方那个 CDN 引入的写法做正式项目,太糙。我的标准做法是用 Vite 起项目,装 three 和几个必要的辅助库:
npm create vite@latest agri-dashboard -- --template vanilla cd agri-dashboard npm install three npm install three/examples/jsm/controls/OrbitControls npm install three/examples/jsm/loaders/GLTFLoader npm install three/examples/jsm/loaders/DRACOLoaderDRACOLoader 一定要装。GLB 用 Draco 压缩后体积能再降一半,代价是加载时多几十毫秒的解压时间,完全值得。
项目结构我习惯这样分:
src/ main.js 入口,初始化场景 scene/ setup.js 场景、相机、渲染器配置 lights.js 光照 loaders/ modelLoader.js 模型加载封装 interaction/ raycast.js 点击拾取 camera.js 相机控制 data/ websocket.js 实时数据接入 ui/ panel.js 信息面板这样分的好处是后面加功能不用翻主文件,每个模块职责清晰。
3.2 场景初始化与光照配置的实操细节
场景初始化有几个参数直接影响观感。渲染器我一般开 antialias,像素比限制在 2 以内,不然高分屏上性能掉得厉害:
const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.outputColorSpace = THREE.SRGBColorSpace;光照是农业大屏的难点。要模拟自然光又不能太暗,我的方案是三层光叠加:一个半球光做环境基础照明,一个平行光做主光源带阴影,再加一个补光从侧面打亮暗部。平行光的阴影相机范围要精确设置,太大阴影糊,太小边缘建筑没影子。
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.2); const dirLight = new THREE.DirectionalLight(0xffffff, 2.0); dirLight.position.set(50, 80, 30); dirLight.castShadow = true; dirLight.shadow.camera.left = -100; dirLight.shadow.camera.right = 100; dirLight.shadow.camera.top = 100; dirLight.shadow.camera.bottom = -100; dirLight.shadow.mapSize.set(2048, 2048);3.3 模型加载与贴图不显示的排查实录
贴图不显示是 Three.js 新手最容易踩的坑,我几乎每个项目都会遇到一次。原因通常有这几类:
路径问题最常见。GLB 里的贴图路径是相对路径,如果模型文件和贴图分开存放,加载时找不到就显示成灰色。解决办法是把贴图打包进 GLB,或者用 LoadingManager 统一管理路径。
色彩空间问题也很隐蔽。贴图加载后如果没设置colorSpace = THREE.SRGBColorSpace,颜色会发灰发暗,看起来像没加载。这个在 Three.js 新版本里是必须手动设的。
材质类型不匹配。有些模型导出时用的是 MeshStandardMaterial,但贴图是给 MeshBasicMaterial 准备的,加载后贴图不生效。检查一下材质的 map 属性有没有正确赋值。
UV 坐标丢失。Tripo3D 生成的模型偶尔会丢 UV,进 Blender 重新展开一次就行。
我的排查顺序是:先看控制台有没有 404,再看材质面板里 map 是否为 null,最后检查 colorSpace。九成问题在前两步就能定位。
3.4 点击拾取与数据面板联动
大屏的核心交互是点哪看哪。用 Raycaster 实现:
const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); window.addEventListener('click', (event) => { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(clickableObjects); if (intersects.length > 0) { const obj = intersects[0].object; showPanel(obj.userData); } });关键在userData。我在 Blender 导出前就给每个模型对象写好了自定义属性,比如{ type: 'greenhouse', id: 'GH-03', name: '三号温室' }。点击时直接读这个数据去请求对应的传感器信息,不用在前端做 ID 映射。
面板我用的是原生 DOM 覆盖在 canvas 上,不用 Three.js 的 CSS2DRenderer。原因是 DOM 面板做动画和响应式布局更灵活,而且不占 WebGL 渲染资源。
4. GPT-6 Astra 的接入与智能巡检实现
4.1 Astra 在农业场景中的角色定位
很多人一听 AI 接入就想到聊天机器人,那是把大材小用了。在我的系统里,Astra 承担的是语义层到数据层的翻译官和异常数据的归因分析师两个角色。
翻译官这个角色解决的是“人话到机器指令”的问题。园区技术员不会写查询语句,但他们会说“帮我看看北边那几个棚今天有没有异常”。Astra 要把这句话解析成:区域筛选条件(北区)、时间范围(今天)、查询目标(异常状态)、输出格式(列表加摘要)。
归因分析师这个角色更有价值。单个传感器报警说明不了什么,但 Astra 能综合多个数据源做交叉判断。比如三号棚温度偏高、湿度偏低、同时水肥一体机的流量数据异常,Astra 会给出“疑似滴灌管路堵塞导致蒸腾加剧”这样的判断,而不是简单报“温度异常”。
4.2 自然语言巡检指令的结构化解析
我设计了一套 prompt 模板,把园区的地块信息、设备清单、数据字段说明作为系统提示词固定下来,用户的自然语言作为用户消息传入。Astra 的输出强制为 JSON:
{ "intent": "inspection", "region": ["north"], "timeRange": "today", "metrics": ["temperature", "humidity", "soil_moisture"], "alertLevel": "warning", "summary": "北区三号棚温度偏高..." }前端拿到这个 JSON 后,按 region 筛选出对应的模型对象,按 metrics 去请求数据接口,按 alertLevel 决定高亮颜色。整个过程用户只说了一句话。
实测下来 Astra 对农业术语的理解相当到位。“墒情”它能正确映射到 soil_moisture,“放风”能理解成通风操作,“打药”能识别为植保作业。这比我自己维护一套关键词映射表省事多了。
4.3 多源数据融合的异常归因分析
这块是我花时间最多的部分。农业数据的异常往往是多因素耦合的,单看一个指标容易误判。我的做法是把同一时间段内的所有相关数据打包成一个结构化描述,让 Astra 做综合分析。
比如这样一段输入:
三号温室,过去两小时数据: 空气温度 32.5°C(阈值上限 30°C) 空气湿度 45%(阈值下限 60%) 土壤含水率 18%(阈值下限 25%) 滴灌流量 0.2L/min(正常值 1.5L/min) 通风口状态:关闭 室外温度 28°CAstra 的输出是:“滴灌流量异常偏低是根因,导致土壤含水率下降,作物蒸腾加剧使空气湿度降低,同时通风口关闭使热量积聚。建议优先检查滴灌管路,其次开启通风口降温。”
这个判断逻辑如果我自己写规则引擎,得写几十条 if-else,还容易漏掉组合情况。Astra 直接给出了因果链,技术员一看就明白该先干什么。
4.4 巡检任务的自动生成与路线规划
基于 Astra 的分析结果,系统会自动生成巡检任务并规划路线。路线规划用的是简单的贪心算法:从当前位置出发,每次选择距离最近且优先级最高的待巡检点。
优先级由 Astra 根据异常严重程度打分。比如滴灌堵塞这种会影响整棚作物的,优先级高于单个传感器离线。路线生成后在 3D 场景里用一条动态发光的线画出来,技术员跟着走就行。
实操心得:Astra 的响应延迟在 1 到 3 秒之间,如果巡检指令很频繁,建议在前端加一个防抖,避免重复请求。另外把常用的查询结果缓存五分钟,能省不少调用成本。
5. 常见问题排查与性能优化实录
5.1 模型加载与渲染类问题速查
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 模型不显示 | 路径错误或格式不支持 | 看控制台报错 | 检查 GLB 路径,确认 loader 正确 |
| 贴图发灰 | 色彩空间未设置 | 检查 material.map.colorSpace | 设为 SRGBColorSpace |
| 模型全黑 | 法线翻转或光照缺失 | 加环境光测试 | 统一法线,补环境光 |
| 模型躺倒 | 坐标轴不匹配 | 检查模型旋转 | 导出时勾选 +Y 向上 |
| 边缘锯齿 | 抗锯齿未开或面数过低 | 看渲染器配置 | 开 antialias,提高模型面数 |
| 帧率骤降 | draw call 过多 | 用性能面板看 | 合并材质,使用 LOD |
5.2 大屏性能优化的五个关键手段
第一,纹理压缩。所有贴图转 WebP 或 KTX2,这是体积优化的最大头。我那个园区场景,贴图从 40MB 压到 6MB,加载时间从十几秒降到三秒。
第二,实例化渲染。园区里重复的物体很多,比如路灯、传感器杆、树木。用 InstancedMesh 把这些合并成一个 draw call,性能提升非常明显。32 栋温室如果结构完全一样,也可以考虑用实例化加不同的变换矩阵。
第三,视锥剔除。Three.js 默认开启,但要确保模型的 boundingSphere 计算正确。有些从 Tripo3D 来的模型包围盒不准,导致该剔除的没剔除,白白渲染。
第四,按需渲染。场景静止时不要一直跑 requestAnimationFrame。我的做法是监听相机变化和交互事件,没有变化时暂停渲染循环,能省不少电和 GPU。
第五,分块加载。整个园区不要一次性全加载,按区域分块,相机移动到哪个区域再加载哪块。配合 LoadingManager 做进度条,体验会好很多。
5.3 数据对接中的典型坑与解法
时间戳对不上是最常见的。传感器上报的是本地时间,服务器存的是 UTC,前端展示又要转回本地。我统一在网关层就转成 UTC 毫秒时间戳,前端按需格式化,避免混乱。
数据频率不一致也头疼。气象站一分钟一条,土壤传感器五分钟一条,水肥机是事件触发。我的处理是在前端做时间对齐,按五分钟粒度聚合,缺失值用前值填充并标记。
断线重连必须做。园区网络不稳定是常态,WebSocket 断了要自动重连,重连后要补拉断线期间的数据。我用的是指数退避重连策略,第一次 1 秒,第二次 2 秒,最多到 30 秒。
数据量控制。一个棚十几个传感器,32 个棚就是几百个数据点,如果全部实时推送,前端处理不过来。我的做法是前端只订阅当前视野范围内的传感器数据,相机移走了就取消订阅。
5.4 从调研到上线的完整时间线复盘
这个项目从进场调研到最终上线,总共用了六周。时间分配大致是:
第一周做现场调研和数据接口对接,把园区现有的设备协议摸清楚,这是最不能省的时间。第二周做模型资产,Tripo3D 生成加 Blender 处理,32 栋温室加配套建筑全部搞定。第三周搭 Three.js 场景,把模型摆进去,调光照和相机。第四周接数据,WebSocket 打通,面板联动做出来。第五周接 Astra,做巡检指令解析和异常归因。第六周整体联调、性能优化、现场部署。
如果重来一次,我会把第二周和第三周并行做,模型生产和场景搭建其实可以同步推进。另外现场调研一定要多待几天,我第一周只待了两天,后面发现有几个泵房的设备型号和清单上写的不一样,返工花了半天。
6. 后续可扩展的方向
这套系统目前跑得挺稳,但还有几个方向我觉得值得继续做。一个是历史数据回放,把过去某个时间段的传感器数据在 3D 场景里重演一遍,对分析病虫害爆发过程很有帮助。另一个是移动端适配,现在主要是在 PC 大屏上用,技术员巡检时如果能用手机打开,边走边看会更方便。
Astra 那边我还在试一个新玩法:让它根据历史数据和气象预报,提前预测未来 24 小时哪些棚可能出现异常,把被动巡检变成主动预防。目前准确率大概七成,还在调 prompt 和补充数据维度。
模型资产这块,Tripo3D 生成农作物我试了几次,效果还不太行,叶片的细节和层次感不够。目前作物还是用简模加贴图的方式,等工具再迭代迭代可能会好一些。
最后分享一个我在这个项目里养成的习惯:每接一个新传感器,先在 3D 场景里把它可视化出来再对接数据。看起来是多余的一步,但能帮你提前发现坐标不对、朝向反了、量程设置错误这些问题。数据接进来之后再发现这些,排查起来就麻烦多了。