Skip to content

别再瞎写 Cesium 特效!扩散墙这样写最流畅,科技感拉满!源码直接拷走复用

接上文,做了个雷达扫描的效果,预警效果的话增加一个扩散墙的效果。

动态扩散效果常用于重点区域预警、电子围栏激活、目标区域提示、应急事件展示等场景,这里基于Cesium原生API,实现圆形多边形扩散墙两种效果。

两套效果均使用 Wall + CallbackProperty 实现,流畅无卡顿,支持Vue/React/原生JS项目直接接入。

实现方案

两种扩散墙共用一套技术逻辑,仅形状生成方式不同:

使用 Cesium.Wall 绘制垂直墙体,然后通过 CallbackProperty 实时更新墙体顶点位置。

控制半径/缩放值从小到大、高度从大到小,形成扩散动画。

最后达到阈值后重置,实现循环扩散。

实现代码

javascript
/**
 * 圆形扩散效果
*/
const createCircleDiffusionEffect = () => {
    if (!cesiumViewer.value) return;
    
    if (!isShowCircleDiffusion.value) {
        // 创建圆形扩散效果
        isShowCircleDiffusion.value = true;
        
        // 定义状态变量
        let center = [117.105356, 36.437555];
        let radius = 140.0;
        let speed = 5.0;
        let height = 30.0;
        let minRadius = 20.0;
        let color = new Cesium.Color(0.0, 0.8, 1.0, 1.0);
        let currentHeight = height;
        let currentRadius = minRadius;
        let entity = null;
        
        // 添加圆形实体
        entity = cesiumViewer.value.entities.add({
            wall: {
                // callbackProperty回调函数,实时更新
                positions: new Cesium.CallbackProperty(() => {
                    let positions = [];
                    // 更新半径和高度
                    currentRadius += (radius * speed) / 1000.0;
                    currentHeight -= (height * speed) / 1000.0;
                    
                    // 判断扩散的实际半径和高度是否超出范围
                    if (currentRadius > radius || currentHeight < 0) {
                        currentRadius = minRadius;
                        currentHeight = height;
                    }
                    
                    // 获取圆形位置点
                    let modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
                        Cesium.Cartesian3.fromDegrees(center[0], center[1], 0)
                    );
                    
                    // 生成圆形顶点
                    for (let i = 0; i < 64; i++) {
                        let angle = (i / 64) * Cesium.Math.TWO_PI;
                        let x = Math.cos(angle);
                        let y = Math.sin(angle);
                        let point = new Cesium.Cartesian3(
                            x * currentRadius,
                            y * currentRadius,
                            currentHeight
                        );
                        positions.push(
                            Cesium.Matrix4.multiplyByPoint(
                                modelMatrix,
                                point,
                                new Cesium.Cartesian3()
                            )
                        );
                    }
                    
                    // 封闭圆形,首节点需要存两次
                    positions.push(positions[0]);
                    return positions;
                }, false),
                // 设置材质 - 使用半透明的颜色
                material: Cesium.Color.fromCssColorString('rgba(0, 204, 255, 0.7)')
            },
        });
        
        // 存储引用
        circleDiffusionRef.value = {
            remove: () => {
                if (entity && cesiumViewer.value) {
                    cesiumViewer.value.entities.remove(entity);
                    entity = null;
                }
            },
            update: (options) => {
                if (options.center) center = options.center;
                if (options.radius) radius = options.radius;
                if (options.speed) speed = options.speed;
                if (options.height) {
                    height = options.height;
                    currentHeight = options.height;
                }
                if (options.color) {
                    color = options.color;
                    if (entity) {
                        entity.wall.material = color;
                    }
                }
            },
            entity: entity
        };
        
        console.log('✅ 圆形扩散效果已创建');
    } else {
        // 移除圆形扩散效果
        isShowCircleDiffusion.value = false;
        
        if (circleDiffusionRef.value) {
            circleDiffusionRef.value.remove();
            circleDiffusionRef.value = null;
        }
        
        console.log('✅ 圆形扩散效果已移除');
    }
    
    // 强制场景更新
    if (cesiumViewer.value && cesiumViewer.value.scene) {
        cesiumViewer.value.scene.requestRender();
    }
}

这里材质上我仅仅使用了半透明的纯色,如果有需要我觉得可以上贴图。效果更好!

javascript
/**
 * 多边形扩散效果
 * */
const createPolygonDiffusionEffect = () => {
    if (!cesiumViewer.value) return;
    
    if (!isShowPolygonDiffusion.value) {
        // 创建多边形扩散效果
        isShowPolygonDiffusion.value = true;
        
        // 定义状态变量
        // 使用电子围栏的坐标作为基础多边形
        const basePolygon = [
            { lon: 117.105951, lat: 36.437923 },
            { lon: 117.105898, lat: 36.437773 },
            { lon: 117.106451, lat: 36.437648 },
            { lon: 117.106506, lat: 36.437808 },
        ];
        
        // 计算多边形中心点
        const calculatePolygonCenter = (vertices) => {
            let sumLon = 0, sumLat = 0;
            for (const vertex of vertices) {
                sumLon += vertex.lon;
                sumLat += vertex.lat;
            }
            return { lon: sumLon / vertices.length, lat: sumLat / vertices.length };
        };
        
        // 计算顶点相对于中心点的偏移量(以米为单位)
        const calculateVertexOffsets = (vertices, center) => {
            const offsets = [];
            for (const vertex of vertices) {
                // 将经纬度转换为笛卡尔坐标
                const vertexCart = Cesium.Cartesian3.fromDegrees(vertex.lon, vertex.lat, 0);
                const centerCart = Cesium.Cartesian3.fromDegrees(center.lon, center.lat, 0);
                
                // 计算偏移向量
                const offset = new Cesium.Cartesian3();
                Cesium.Cartesian3.subtract(vertexCart, centerCart, offset);
                offsets.push(offset);
            }
            return offsets;
        };
        
        const center = calculatePolygonCenter(basePolygon);
        const vertexOffsets = calculateVertexOffsets(basePolygon, center);
        
        let maxScale = 5; // 最大缩放比例
        let speed = 5; // 扩散速度
        let height = 30.0; // 初始高度
        let minScale = 1.0; // 最小缩放比例
        let color = new Cesium.Color(1.0, 0.2, 0.2, 1.0); // 亮红色
        let currentHeight = height;
        let currentScale = minScale;
        let entity = null;
        
        // 添加多边形实体
        entity = cesiumViewer.value.entities.add({
            wall: {
                // callbackProperty回调函数,实时更新
                positions: new Cesium.CallbackProperty(() => {
                    let positions = [];
                    // 更新缩放比例和高度
                    currentScale += (maxScale * speed) / 1000.0;
                    currentHeight -= (height * speed) / 1000.0;
                    
                    // 判断扩散的实际缩放比例和高度是否超出范围
                    if (currentScale > maxScale || currentHeight < 0) {
                        currentScale = minScale;
                        currentHeight = height;
                    }
                    
                    // 生成扩散后的多边形顶点
                    for (let i = 0; i < vertexOffsets.length; i++) {
                        const offset = vertexOffsets[i];
                        
                        // 计算缩放后的偏移量
                        const scaledOffset = new Cesium.Cartesian3();
                        Cesium.Cartesian3.multiplyByScalar(offset, currentScale, scaledOffset);
                        
                        // 计算最终位置(中心点 + 缩放后的偏移量)
                        const centerCart = Cesium.Cartesian3.fromDegrees(center.lon, center.lat, currentHeight);
                        const position = new Cesium.Cartesian3();
                        Cesium.Cartesian3.add(centerCart, scaledOffset, position);
                        
                        positions.push(position);
                    }
                    
                    // 封闭多边形,首节点需要存两次
                    positions.push(positions[0]);
                    return positions;
                }, false),
                // 设置材质 - 使用亮红色
                material: Cesium.Color.fromCssColorString('rgba(255, 0, 0, 0.7)')
            },
        });
        
        // 存储引用
        polygonDiffusionRef.value = {
            remove: () => {
                if (entity && cesiumViewer.value) {
                    cesiumViewer.value.entities.remove(entity);
                    entity = null;
                }
            },
            update: (options) => {
                if (options.maxScale) maxScale = options.maxScale;
                if (options.speed) speed = options.speed;
                if (options.height) {
                    height = options.height;
                    currentHeight = options.height;
                }
                if (options.color) {
                    color = options.color;
                    if (entity) {
                        entity.wall.material = color;
                    }
                }
            },
            entity: entity
        };
        
        console.log('✅ 多边形扩散效果已创建');
    } else {
        // 移除多边形扩散效果
        isShowPolygonDiffusion.value = false;
        
        if (polygonDiffusionRef.value) {
            polygonDiffusionRef.value.remove();
            polygonDiffusionRef.value = null;
        }
        
        console.log('✅ 多边形扩散效果已移除');
    }
    
    // 强制场景更新
    if (cesiumViewer.value && cesiumViewer.value.scene) {
        cesiumViewer.value.scene.requestRender();
    }
}

四、总结

两种最常用的动态扩散墙特效

  • 圆形扩散墙:适合点位预警、半径扫描、设备监控。
  • 多边形扩散墙:适合电子围栏、区域警戒、不规则范围提示。

如果是生产环境我建议可以增加消除机制,避免出现无限制循环的情况发生。