文章目录
无人机航道案例把离散航点连接成空心透明管道,并叠加边缘辉光和沿路径移动的流光。正常、告警和阻断状态使用不同颜色表达。

实现思路导图
flowchart TD
ROOT[无人机三维航道]
ROOT --> PATH[路径准备]
PATH --> ENU[转为 ENU 局部坐标]
PATH --> DENSE[插值加密与累计距离]
ROOT --> FRAME[并行移动标架]
ROOT --> TUBE[管状网格]
TUBE --> RING[圆形截面环]
TUBE --> INDEX[环间三角形与 UV]
ROOT --> MATERIAL[流光材质与告警状态]
ROOT --> DRONE[无人机等速动画]
数据对象不依赖 Cesium UI
CorridorData 保存航点、半径、分段数、透明度、流速和状态。页面先创建三个示例,再交给 CorridorManager:
const corridor = CorridorData.createDefault(options);
corridor.applyStatusColor();
manager.add(corridor);
这种数据对象可以序列化,也便于把业务状态和 Cesium Primitive 分开。
从中心线生成 Tube
离散航点先平滑插值。对中心线每个采样点计算切线,再建立与切线垂直的局部截面。圆周上的相邻点和前后截面连接成三角形。
管道分段数控制截面圆滑度,路径细分数控制弯道平滑度。两者相乘决定顶点量,不能为了视觉平滑无限提高。
const geometry = TubeGeometryBuilder.build(corridorData);
if (!geometry) return;
材质表达空心、辉光和流动
片元 Shader 使用视线方向与法线夹角计算 Fresnel,靠近轮廓的片元更亮。流光坐标来自沿路径方向的 UV:
float rim = pow(1.0 - abs(dot(normal, viewDir)), glowPower);
float flow = fract(v_st.x * flowDensity - time * flowSpeed);
hollowness 降低管道正面中心的不透明度,让航道看起来像能穿行的空间,而不是实心圆柱。
选择与状态切换
Primitive 使用航道 id 作为拾取标识。左键拾取后,面板展示该航道的快照,修改参数再由 Manager 更新几何或 uniform。
半径、截面数和路径平滑会改变几何,需要重建;颜色、透明度、流速和方向可以只更新材质。把两类参数分开能避免拖动滑块时持续重建网格。
路径坐标
航点以经纬度和绝对高度保存。长距离航道需要注意局部坐标基准和地球曲率;本案例是城市尺度,逐点转换为 ECEF 后建立截面即可满足展示。
实现主线是:航点数据 → 平滑中心线 → Tube 三角网 → 菲涅尔与流光材质 → Manager 管理选择、状态和生命周期。
截图中的三条航道表达三种状态
蓝色是正常主航道,黄色是告警航道,红色是阻断航道。白色点是中间航点,绿色和红色端点分别表示起点、终点。截图来自示例数据初始化完成后,不需要用户再绘制才能理解结果。
面板修改的是当前选中航道。点击管道时,Cesium 拾取返回 Primitive 的 id,Manager 再用 id 找到 CorridorData,不会从渲染几何反推业务参数。
平滑中心线怎样避免拐角扭曲
原始航点数量少,直接连接会形成折线。案例先在相邻航点间插值,再为每个中心点估计切线。截面需要一组连续的法线、副法线基;如果每个点都独立用世界 Z 轴叉乘,切线接近 Z 轴时截面会突然翻转。
稳定做法是从前一截面传播方向,必要时使用最小旋转更新局部标架。闭合路径还要在首尾之间分配累积扭转,否则接缝处会出现明显旋转跳变。
Tube 顶点包含哪些属性
每个截面有 tubeSegments 个圆周点。相邻截面连接后,顶点至少需要:
position:管道表面位置;normal:用于 Fresnel 和光照;st:横向圆周坐标与沿路径累计坐标;- 可选的路径进度或状态属性。
沿路径的 UV 应按累计距离归一化,而不是按采样点索引。这样不同密度的平滑点不会让流光忽快忽慢。
空心效果不是删除正面片元
hollowness 根据法线与视线夹角降低正面 alpha,轮廓处仍保持可见。它产生“透明管道”的视觉,但几何仍是一层外壳,没有真实内壁。
若要进入管道内部观察,需要关闭背面剔除或创建内壁,并正确处理深度排序。当前案例使用透明混合且 depthMask: false,多条航道交叉时仍可能出现顺序相关的透明问题。
哪些修改需要重建 Primitive
航点、半径、圆周分段和路径平滑会改变顶点,CorridorRenderer.update 会移除旧 Primitive 并重建。颜色、流速、流向、密度和亮度只改变 uniforms,可保留几何。
状态切换同时改变业务 status 和配色。隐藏航道会从 Manager 的可见集合中移除渲染,但数据对象仍保留,重新显示时可以重建。
时间如何驱动流光
渲染器在场景更新时累加秒数并写入 u_time。流光相位是路径 UV、密度、方向和时间的组合。使用真实 deltaTime 能让 60Hz 与 144Hz 屏幕看到相同流速。
页面卸载时必须解除场景监听。否则 u_time 更新闭包会继续引用已经移除的 Appearance 和 Viewer。
从零复现:无人机三维航道教程
案例源码位于 src/demos/cesiumdemos/uav-corridor/。管道几何的难点不是画圆,而是让圆形截面沿弯曲路径连续旋转而不突然翻面。
第 1 步:准备航线与局部坐标
将经纬高控制点转换为 Cartesian3,再以首点或路径中心建立 ENU 参考系,把全部点转为局部米制坐标。
const localPoint = Cesium.Matrix4.multiplyByPoint(
inverseFrame,
worldPoint,
new Cesium.Cartesian3(),
);
完成标志:局部路径长度与 Cesium 测距接近,高度差保留,坐标量级不再是数百万米。
第 2 步:加密路径
按最大分段长度对相邻控制点插值。弯曲航线可以使用 Catmull-Rom,但必须限制过冲,避免曲线穿过地面或越过禁飞区。
每个采样点保存累计距离。后续 UV、无人机动画和流光速度都使用这条统一距离轴。
第 3 步:构建并行移动标架
首点根据切线选择一个不平行的参考向量,叉乘得到法线与副法线。后续点把上一帧标架绕相邻切线的最小旋转传递过去。
tangent[i] = normalize(path[i+1] - path[i-1])
normal[i] = transport(normal[i-1], tangent[i-1], tangent[i])
binormal[i] = normalize(tangent[i] × normal[i])
不要每个点都用世界 Z 轴重新叉乘。路径接近竖直时会退化,并产生管道截面突然翻转。
第 4 步:生成截面环
对每个路径点按 radialSegments 生成圆环:
vertex =
center +
normal * Math.cos(angle) * radius +
binormal * Math.sin(angle) * radius;
相邻两个环按索引连接成四边形,再拆成两个三角形。UV 的 U 使用圆周比例,V 使用累计距离比例。
第 5 步:制作半透明流动材质
片元 Shader 根据 V 坐标、密度、方向和 u_time 生成移动亮带。状态颜色、透明度和速度使用 uniform,管径和径向分段属于几何参数。
深度测试保持开启,透明度小于 1 时通常关闭深度写入,并按相机距离或明确 render order 处理多条航道排序。
第 6 步:让无人机沿统一距离轴运动
用速度乘时间得到路径距离,二分查找所在分段并插值位置。航向使用当前切线,姿态再叠加机体轴向修正。
const distance = (elapsed * speed) % totalLength;
const segment = findSegmentByDistance(distance);
这样路径几何、流光和无人机速度都以米为基准,不会因某段控制点更密而忽快忽慢。
第 7 步:区分更新成本
颜色、透明度、流速只改 uniform;管径和截面分段重建几何;控制点变化要重新加密、计算标架、构建网格和累计距离。面板使用 change 与 finishChange 区分实时预览和昂贵重建。
验收与排错
- 管道在某个弯道扭转 180°:检查并行移动标架。
- 无人机速度忽快忽慢:动画应按累计距离而不是点索引插值。
- 半透明管道互相遮挡错误:检查深度写入和透明排序。
- 路径修改后流光断裂:重新计算连续 UV 与总长度。