在数字孪生和智慧城市项目中我们经常需要模拟雷达扫描、信号覆盖或能量场扩散的效果。简单的做法可能是使用GIF贴图但这往往缺乏立体感且性能不佳。今天我们将深入解析一段高质量的 Cesium 代码教你如何通过混合使用 Entity 和 Primitive并利用矩阵变换Matrix Transformation在 3D 场景中实现一个流畅的“旋转圆柱体”雷达扫描效果。 核心架构为什么选择混合模式在开始写代码之前我们需要思考一个问题如何让一个 3D 物体动起来Cesium 提供了多种方式而这段代码采用了“动静分离”的设计哲学背景Entity使用Entity绘制一个静态的、半透明的蓝色圆柱体。因为 Entity 写法简单且适合处理不需要频繁重绘的静态对象。扫描头Primitive使用Primitive绘制一个 45° 的扇形圆柱。Primitive 基于底层 API配合ModelMatrix矩阵变换可以避免每一帧都重新计算几何顶点从而实现极致的高性能旋转。️ 代码深度解析让我们逐层拆解createRotatingCylinders函数的实现细节。第一步构建静态能量场Entity代码首先创建了一个完整的蓝色圆柱体作为“底座”。实现方式利用viewer.entities.add。关键点position: 通过fromDegrees设置中心点并将高度设为height / 2确保圆柱体在地表之上居中显示。material: 使用了 RGBA 格式的蓝色(0.0, 0.6, 1.0, 0.4)0.4 的透明度让它看起来像充满能量的力场。第二步创建动态扫描扇叶Primitive这是效果的核心。代码没有使用现成的几何体而是构建了一个自定义的扇形圆柱几何体SectorCylinderGeometry。几何定义startAngle: 0到endAngle: Cesium.Math.PI_OVER_FOUR即 45°。这意味着我们只渲染圆柱体的八分之一一个扇叶。自定义着色器代码中内联了 Vertex Shader 和 Fragment Shader。Vertex Shader负责计算顶点位置并传递颜色。Fragment Shader直接输出颜色。这种底层控制允许我们未来轻松扩展为渐变色或辉光效果。性能优势Primitive 在这里只是一块“死板”它本身不包含位置信息而是通过外部的矩阵来告诉它“在哪里”以及“怎么摆放”。第三步矩阵旋转动画黑科技所在这是本篇博客最硬核的部分。代码没有通过修改经纬度来移动物体那样计算量巨大而是通过矩阵相乘来实现旋转。代码逻辑拆解建立局部坐标系Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian)。这一步非常关键它建立了一个以圆柱中心为原点且 X轴向东、Y轴向北、Z轴向上的局部坐标系。这保证了后续的旋转是围绕 Z 轴进行的而不是歪的。计算旋转矩阵在scene.preUpdate事件中利用Cesium.Math.PI相关的三角函数生成旋转矩阵rotationMatrix。矩阵融合ModelMatrix LocalFrame × RotationTransform。这里的乘法不是简单的数字相乘而是将“旋转动作”叠加到了“初始位置”上。应用变换将计算出的modelMatrix赋值给quarterPrimitive.modelMatrix。视觉效果Primitive 每一帧都接收到一个新的矩阵指令告诉它“绕着中心点转一点角度”。由于 Primitive 的顶点数据在 GPU 中是静态的这种变换几乎不消耗 CPU 性能即使同时运行成百上千个雷达帧率也能保持稳定。 核心函数与参数对照表为了方便开发者查阅我整理了该函数的关键参数与技术点表格参数/模块默认值/类型作用说明技术原理centerLon/Lat116.39...扫描中心点坐标WGS84 经纬度ModelMatrixMatrix4控制 Primitive 位置与旋转Model-View-Projection 矩阵变换preUpdateEvent动画驱动循环Cesium 渲染管线的生命周期钩子destroy()Function资源回收移除监听器与图元防止内存泄漏rotateSpeed0.1旋转速度弧度制增量控制sin/cos计算频率 开发者心得关于createSectorCylinderGeometry代码中引用了createSectorCylinderGeometry这是一个外部依赖函数通常需要你自己实现或引入第三方库。它本质上是通过循环计算圆周上的顶点segments: 64决定了圆滑度来构建侧面和顶面的三角网。如果你在项目中使用此代码需要确保这个几何体生成器已定义。关于销毁机制代码非常规范地实现了destroy函数。在preUpdate中添加的监听器必须手动移除preUpdateListener()否则即使图层关闭动画逻辑仍在后台运行会导致内存泄漏。扩展性你可以轻松修改endAngle来改变扇叶的宽度或者修改rotateSpeed的正负值来控制顺时针或逆时针旋转。如果想做成双扇叶雷达可以在几何体定义中增加一个反向的扇形即可。export function createRotatingCylinders( viewer, { centerLon 116.394694, centerLat 39.905268, radius 1000, height 100, rotateSpeed 0.1, } {} ) { const scene viewer.scene; // 1. 静态蓝色完整圆柱Entity const fullCylinderEntity viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, height / 2), cylinder: { length: height, topRadius: radius, bottomRadius: radius, material: new Cesium.Color(0.0, 0.6, 1.0, 0.4), // 蓝色 outline: false, }, }); // 2. 动态 45° 扇形柱体Primitive const centerCartesian Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 0); const sectorGeometry createSectorCylinderGeometry({ radius, height, startAngle: 0, endAngle: Cesium.Math.PI_OVER_FOUR, segments: 64, }); const instance new Cesium.GeometryInstance({ geometry: sectorGeometry, modelMatrix: Cesium.Matrix4.IDENTITY, }); const quarterPrimitive new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.Appearance({ translucent: true, closed: true, renderState: Cesium.Appearance.getDefaultRenderState(true, true), vertexShaderSource: in vec3 position3DHigh; in vec3 position3DLow; in vec4 color; in float batchId; out vec4 v_color; void main() { vec4 p czm_computePosition(); v_color color; gl_Position czm_modelViewProjectionRelativeToEye * p; } , fragmentShaderSource: in vec4 v_color; void main() { out_FragColor v_color; } , }), asynchronous: false, }); scene.primitives.add(quarterPrimitive); // 3. 动态旋转逻辑 let angle 0; let isDestroyed false; const localFrame Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian); quarterPrimitive.modelMatrix localFrame; const preUpdateListener scene.preUpdate.addEventListener(() { if (isDestroyed) return; angle rotateSpeed; const rotationMatrix Cesium.Matrix3.fromRotationZ(angle); const rotationTransform Cesium.Matrix4.fromRotationTranslation(rotationMatrix); const modelMatrix Cesium.Matrix4.multiply( localFrame, rotationTransform, new Cesium.Matrix4() ); quarterPrimitive.modelMatrix modelMatrix; }); // 销毁函数 const destroy () { if (isDestroyed) return; isDestroyed true; preUpdateListener(); viewer.entities.remove(fullCylinderEntity); scene.primitives.remove(quarterPrimitive); }; return { destroy }; }