面板上有一个滑块,标签写"流动速度",值是 3。
3 什么?3 米每秒?3 圈每秒?3 个像素每秒?滑块旁边没有单位,源码里也没有注释。
上一回我们在空气里发射线数云,这回回到地面上来——往地上立一堵墙,让条纹沿着墙面往上爬。看着简单,但"往上爬"这三个字背后藏着三个数:爬多快、条纹多粗、沿哪个方向爬。这三个数本猿一个都没猜对。
三维特效这条线从今天开篇。它跟前面几条线不太一样:缓冲区、插值、可视域量的是"结论对不对",特效这条线量的是"画面跟你以为的是不是同一回事"——面板调出来的观感,和着色器里真正算出来的东西,中间往往隔着一层。
我是 Cesium酱,一只在 WebGIS 领域摸爬滚打多年的前端猿。这个系列写到第 46 篇,本猿的规矩还是那句:只要面板上有数字,就得拿进 GPU 里对一遍账。牛马不易,忙忙停停,但每次量出"原来你算的是这个",那点兴奋劲还在。
本文目标:从一堵会流动的围墙出发,把WallGeometry的st坐标、czm_frameNumber驱动的时间、以及MaterialAppearance的半透明状态,三笔账一次算清。💪
💡 本文涉及的全部代码均已开源,完整的仓库地址见文末,可自行取用、随意魔改。
一、痛点与场景:为什么"围墙"值得单写一篇?
🎯 背景现状:在三维场景里画一圈"墙",是电子围栏、工地围挡、管控区边界、淹没范围立面、管线迁改红线这些需求的第一反应。它比面多一个高度维度,比线多一个可见的侧立面——看一眼就知道"边界在哪、有多高"。
典型场景:
- 🏗️工地围挡:沿征地红线立一圈 2 米高的蓝皮围挡,还要能看出"这是一圈"而不是一条线
- 🚧电子围栏:重点区域画一圈发光边界,越界报警时整圈变色
- 🌊淹没立面:水位面抬到 15 米,给水体加一圈侧壁,才看得出"水深"是多少
- 🛡️管控区:疫情防控、安保封控的边界,要求远远就能看见一道流动的光墙
🤦 传统痛点:
| 方案 | 问题 |
|---|---|
Entity+WallGraphics | 材质只能用内置那几种,想做"流动"得上MaterialProperty自己写回调,改一个参数整圈重建 |
PolylineVolume | 有粗细有截面,但st是沿截面周长 + 沿线长,条纹方向跟墙体对不上 |
贴地Polygon+ 图片材质 | 只有"顶面",没有立面;墙根本不存在 |
直接改Primitive的modelMatrix | 那是整墙平移旋转,不是纹理流动 |
💡 本文方案:
1️⃣WallGeometry出几何:maximumHeights/minimumHeights两个数组决定墙顶墙底,想把墙抬离地面只要改后者
2️⃣自定义Material写流动:fabric.source里 12 行 GLSL,靠czm_frameNumber推进相位,零 JS 开销
3️⃣MaterialAppearance上屏:translucent: true开混合,closed: false保留双面
✨ 一句话总结:把墙当成一张竖着的画布,把时间当成一个计数器,让纹理坐标自己减下去——流动就成了。
二、核心技术原理:WallGeometry的st到底怎么排
2.1 一面墙是怎么"长"出来的
WallGeometry的输入特别朴素:一串经纬度折点 + 每个折点的顶高和底高。但输出的顶点排布有个反直觉的地方——每一段墙的 4 个顶点是独立的,不跟邻段共享。
本猿拿WallGeometry.createGeometry()实测了一遍(@cesium/engine,一条正东西向直线,墙高 0→100 m):
| 折点数 N | 顶点数 | index 数 | 三角面数 |
|---|---|---|---|
| 2 | 4 | 6 | 2 |
| 3 | 8 | 12 | 4 |
| 4 | 12 | 18 | 6 |
| 5 | 16 | 24 | 8 |
规律是顶点 = 4×(N−1)、index = 6×(N−1)、三角面 = 2×(N−1)。3 个折点本该 6 个顶点,实测 8 个——中间那个折点被拆成了两份(v2/v3 与 v4/v5 坐标完全相同),因为第 1 段和第 2 段各自要一份自己的顶点。
这么做的好处是每段可以独立设置顶底高度,坏处是顶点数约为"共享顶点"版本的 2 倍。对围墙这点量来说无所谓。
2.2 完整流程
核心亮点:第 4 步"每帧推进相位"是本文的杀手锏 🔪——整条时间链路上没有任何 JS 参与,全靠一个 GPU 自动 uniform 自增。
2.3 为什么用 Primitive 而不是 Entity
| 方案 | 原理 | 灵活性 | 复杂度 |
|---|---|---|---|
Entity+WallGraphics | 由GeometryVisualizer代管,走MaterialProperty | 🐢 材质受限于MaterialProperty体系 | ⚡ 低,几行搞定 |
Primitive+GeometryInstance | 自己管生命周期,直接吃Material实例 | 🚀 任意 GLSL,任意 uniform | 🔧 中,要自己处理重建 |
简单说:想要"改一行 GLSL 就换一种观感",就得绕过MaterialProperty,直接持有Material。
三、代码实战:三个函数撑起一堵墙
⚠️ 温馨提示:为了方便,本篇只拆"几何 → 材质 → 回刷"这条主链路,绘制交互(左键加点 / 右键或双击收尾)沿用第 40 篇量过的同一套机制,不再重复。
3.1 造几何 + 造材质
functionbuildWall(lonLats:number[]):Primitive{constpositions=Cartesian3.fromDegreesArray(lonLats)constcount=lonLats.length