Appearance
连栋温室的一轮昼夜:光照、通风窗与 55 天长势
初始化中…
这篇的场景不大,但它同时跑着两条时间轴:一条是 36 秒走完的 24 小时日照,一条是同一轮里从定植长到采收的 55 天。两条轴叠在一起,读数、光照、开窗、生长各自的口径就特别容易串味——HUD 写着 45° 开窗、画面里窗板却压进棚顶,就是这么来的。
场景里有什么
- 棚体:3 跨 × 8 开间,覆盖 28.8 × 32 m,肩高 3.2 m、脊高 4.6 m,单跨屋面坡度 16.3°。
- 骨架:立柱 36 根、天沟 4 条通长、人字斜梁 54 根、脊檩 9 条,全部实例化或通长盒。
- 覆盖:四周 4 块侧墙加每跨两坡共 6 块屋面板,透明度 0.2、
depthWrite: false。 - 通风窗:6 扇铰接在脊上,随太阳高度角开合,正午开度最大 24°。
- 栽培:9 条栽培床、滴灌带 18 条、作物 234 株,茎与冠两次实例化,逐日长大并同时把颜色从嫩绿混到熟绿。
- 补光与喷灌:3 条灯带(夜间亮紫光谱)、2 组点光源;900 个雾滴做下落循环,夜间停灌。
- 配套:场外机耕道、相邻田块 5 片、水肥罐 3 只。
交给 AI 的提示词(我固定分四组写)
总体与尺度
- 纯程序化生成,不引入外部模型与贴图;几何加材质参数,作物用茎干加冠层两级实例化表示。
- 尺寸按真实量纲给死:单跨 9.6 m、开间 4.0 m、肩高 3.2 m、脊高 4.6 m、屋面坡度由
atan2(脊高−肩高, 单跨一半)推出来,不许手填一个"看着像"的角度。 - 明确区分两条时间轴:一轮 = 一次 24 小时日照循环,同一轮内株高从定植长到采收;HUD 里两者都要写清楚,不能混着报。
骨架与覆盖
- 立柱、天沟、人字梁、脊檩按网格批量生成;人字梁必须落在屋面斜线上,倾角用屋面坡角,两坡符号相反。
- 屋面板与侧墙用同一种半透明膜材质,
depthWrite: false并留出双面试样,否则从棚内看屋面会消失。 - 通风窗铰接在脊上:窗板几何先沿开向偏移半个宽度(让铰链落在脊上而不是窗中心),再绕铰链转;开 = 从屋面向上掀起,所以角度是
−坡角 + 开度。
栽培与生长
- 栽培床按跨内等距排布,滴灌带贴在床面两侧;作物槽位用确定性的行内错位,刷新多次位置不变。
- 株高与冠幅随生长进度缩放,允许每株有一个固定的抖动系数让田面不齐平;颜色用实例色,从嫩绿插值到熟绿,和尺寸同源。
- 生长矩阵只在进度跨越阈值时重算(把进度量化到 60 档比较),别每帧重刷 234 × 2 个实例。
- 补光灯夜间要是紫光谱,不能一句"变亮"就把白炽色拉满;喷灌粒子夜间归零不模拟。
镜头与读数
- 机位在场外俯视与棚内低角之间切换,必须用渐变过渡,不许 0/1 阶跃。
- 太阳按高度角与方位角走一整圈,方向光强度随高度角截到 0,半球光同步压暗,天空色与雾色跟同一份颜色走。
- 拖拽随时接管,5 秒缓动回自动机位;卸载时几何、材质、控制器全部释放。
- 零控件,读数写在左下角:模拟时刻、太阳高度角、生长天数、株数、通风窗开度、补光与喷灌状态、棚体尺寸、三角面、帧耗时。
实现里几个值得记的点
"掀开"和"压进去"只差一个符号。 通风窗我写的是 hinge.rotation.z = dir * (-roofAngle - open),直觉上"多减一点就是开大一点"。错。窗板几何沿开向偏移了半宽,铰链在脊上,闭合姿态本来就等于屋面坡角 −roofAngle;再往负方向转,窗板是朝屋面里压,正午最"开"的时候恰好完全嵌进棚顶板里。正确的做法是往正方向回:−roofAngle + open。dir = -1 那一侧同理,符号跟着 dir 走,不是跟着"开度"走。
改之前先确认哪一侧错了。 屋面坡板和人字梁用的是同一套 rotation.z = dir * -roofAngle,我看到通风窗出错时,第一反应是把这三处一起改成 +。停下来推了一遍:屋面板和梁是从脊往檐口下坡,−坡角 对 dir=+1 就是往下斜,本来是对的;通风窗是从脊往掀起方向,同一个式子语义正好相反。结论是只有窗要改,屋面和梁一行都不许动。这一步要是省了,整个棚顶会散架。
读数不能报一个画面里不存在的角度。 HUD 原本写 open / 0.42 * 45 度,把最大开度硬翻译成 45°。但 open 是弧度,0.42 rad 实测 24°。现在直接 open * 57.3,读数就是几何真值。这类"换算系数写死在文案里"的坑,只要物理量纲改过一次就会立刻说谎。
补光灯亮起来应该是紫的。 M.lamp.color.setScalar(night ? 1 : 0.06) 把 RGB 三个通道一起拉满——那就是白光,跟“植物工厂紫光谱补光”完全两回事。现在直接 setHex 一个紫(红蓝高、绿低),配合本来就有的一组紫点光源,夜里棚内才像补光而不是像照明。
机位阶跃等于每轮甩一次镜。 原来的写法是 p < 0.4 ? 0 : p < 0.7 ? 1 : ...,kk 在两个值之间硬跳。因为相机用了指数平滑,跳变会被摊成一两秒的急速横移,看上去像剪辑事故。现在把进出棚写成两个 smoothstep 相减:clamp01((p-0.32)/0.12) - clamp01((p-0.72)/0.12),滑进来、停一会儿、滑出去。
两条时间轴要在读数里分开。 HUD 原来最后一行写"一轮 36 秒 = 一昼夜",可同一句上面还有"生长第 40 / 55 天"。看的人会算:一昼夜怎么长 55 天?现在写成"一轮 36 秒 = 一昼夜 + 55 天长势",明确告诉读者这一轮里压缩了两件事。
生长与颜色必须同源。 实例色用 young.lerp(ripe, g),g 就是缩放用的那个生长量,所以"变深"和"变高"是同一帧同一系数驱动,不会出现长了但没熟、或者绿了但还矮的情况。顺带一个细节:setColorAt 第一次调用才会建 instanceColor,所以刷完必须判空再 needsUpdate,直接取会踩 undefined。
夜间要停的东西不止灯。 雾滴的可见度原来跟"是否在棚内"绑着,夜里照样飘 900 颗白点,在深蓝天光下像下虫子。现在 mistMat.opacity = night ? 0 : 0.75,且跳过整个更新循环——反正看不见,省下这部分逐帧写 buffer 的开销。
按交付要求核对
| 要求 | 实际做到 | 判定 |
|---|---|---|
| 零外部素材 | 全几何 + 材质参数 | 达成 |
| 真实量纲 | 9.6 / 4.0 / 3.2 / 4.6 m,坡角由公式推 | 达成 |
| 通风窗掀向正确 | −坡角 + 开度,屋面板与梁不动 | 达成 |
| 开度读数真实 | 按弧度换算,最大值 24° | 达成 |
| 双时间轴口径 | 昼夜与长势在读数里分开写明 | 达成 |
| 紫光谱补光 | 夜间灯带为紫色,配 2 组点光源 | 达成 |
| 机位无跳变 | smoothstep 渐变进出棚 | 达成 |
| 生长与颜色同源 | 同一个 g 驱动缩放与插值 | 达成 |
| 布局确定性 | 作物槽位用固定行内错位,刷新一致 | 达成 |
| 文字读数无控件 | 五项读数,页面无按钮 | 达成 |
| 卸载清理 | 几何、材质、控制器全部释放 | 达成 |
已知不足
- 灯带是一根 0.5 m 宽的板条。 它不是错,是偷懒。相机进棚仰视时,这条 29 m 长、悬在作物上方 2.95 m 的深灰板会被透视拉成一根从画面顶部插到远处的巨大楔形,一点都不像灯具。真该做的是把它拆成一节节的吊管加吊线。
- 作物是抽象的。 茎加一颗多面体当冠层,没有叶片、没有果实、没有倒伏,采收前看不出"这是什么作物"。
- 开窗机构是假的。 真实文洛温室是齿条推杆带动一批窄窗扇沿脊两侧交错开合,这里每侧一整条 6 m 板,只是为了表达开合状态。
- 补光只有 3 条灯带 2 个光源。 点光源没按跨数铺开,中间那跨其实照不到;也没有按日照积分自动调节补光时长。
- 没有环流与湿度闭环。 通风机、湿帘、内遮阳、外遮阳一个都没做;喷雾只是粒子下落,不参与任何读数。
- 生长模型单调。 只按进度线性长高,没有昼夜温差影响株型、没有强光抑制徒长,55 天里除了绿和高的变化看不出农艺逻辑。
- 覆盖膜不老化。 结露、积尘、透光率衰减都不随时间变化,夜间从外面看棚体永远是同一个透明度的壳。
- 没有出入口。 侧墙四面封死,没有人员门、物流门和缓冲间,所以这台孪生不能表达"人怎么进去"。
- 后台标签页里时间会走得极慢。 帧间时间截在 0.05 秒,浏览器把标签页降频后一轮 36 秒要拖很久,切回前台才恢复。
本站所有 3D 孪生场景、可视化系统均为个人技术实验 Demo,仅用于技术学习与效果展示,不属于对外运营平台,不提供任何商业服务。