CesiumJS 实战:打造动态呼吸脉冲围栏(含发光线与闪烁点)
在三维地理可视化项目中电子围栏Geo-fence是一个常见需求。但静态围栏缺乏视觉冲击力。本文将带你使用CesiumJS构建一个带呼吸脉冲效果的动态围栏系统包含墙体呼吸动画底部发光轮廓线✨顶部闪烁警示点并提供完整可复用代码适用于安防、区域监控、智慧城市等场景。 技术栈说明技术用途Cesium.WallGeometryPrimitive构建墙体几何体支持自定义 Shader 动画PolylineGlowMaterialProperty实现底部发光线条Entity APICallbackProperty SVG 图标实现顶部动态闪烁点自定义 GLSL 材质实现呼吸脉冲效果为什么混合使用 Primitive 和 EntityPrimitive适合高性能、自定义 Shader 的几何体如墙体Entity适合简单图形、内置材质如发光线条、图标 最终效果预览组件效果描述墙体深蓝色半透明墙体亮度和透明度随时间正弦波动呼吸感底部线蓝色辉光线条宽度 5px柔和发光顶部点红色圆形 SVG 图标每个点独立闪烁相位不同✅ 支持任意多边形围栏只需传入坐标数组 核心代码详解1️⃣ 定义围栏坐标与高度首先我们定义围栏的角点坐标和高度。这些数据决定了围栏的位置和大小。// 定义围栏的四个角点按顺序连接形成闭合多边形 const fencePositions [ { lon: 116.397194, lat: 39.909768 },// 第一个角点左下 { lon: 116.397194, lat: 39.900768 },// 第二个角点右下 { lon: 116.392194, lat: 39.900768 },// 第三个角点右上 { lon: 116.392194, lat: 39.909768 }// 第四个角点左上 ]; // 设置围栏墙体的垂直高度单位米 const fenceHeight 60.0;⚠️ 注意最后一个点会自动连接到第一个点形成闭环。2️⃣ 呼吸脉冲材质GLSL Shader这是整个效果的核心我们通过自定义 GLSL 片元着色器实现墙体的呼吸动画。// Cesium 自定义材质入口函数必须命名为 czm_getMaterial czm_material czm_getMaterial(czm_materialInput materialInput) { // 初始化一个默认材质结构体 czm_material material czm_getDefaultMaterial(materialInput); // 获取当前帧号并乘以时间系数控制动画速度值越小越慢 float time czm_frameNumber * 0.01; // 生成 [0, 1] 范围内的正弦波动值模拟“呼吸”节奏 float pulse 0.5 0.5 * sin(time); // 定义基础颜色深蓝色R0, G0.6, B1.0 vec3 baseColor vec3(0.0, 0.6, 1.0); // 透明度随 pulse 波动最小 0.3最大 0.9避免完全透明或不透明 float alpha 0.3 0.6 * pulse; // 设置漫反射颜色物体主色 material.diffuse baseColor; // 设置透明度需配合 ALPHA_BLEND 才生效 material.alpha alpha; // 设置自发光强度随 pulse 增强最大为原色的 1.8 倍增强视觉冲击 material.emission baseColor * pulse * 1.8; // 返回最终计算出的材质 return material; }参数作用可调范围0.01动画速度越小越慢0.3 0.6 * pulse透明度范围[0.3, 0.9]pulse * 1.8发光强度越大越亮3️⃣ 构建墙体使用 Primitive为了高性能渲染我们使用Primitive而非Entity来构建墙体。// 遍历每一条边从第 i 个点到第 i1 个点 for (let i 0; i fencePositions.length; i) { // 获取当前边的起点 const start fencePositions[i]; // 获取当前边的终点% 实现闭环最后一个点连回第一个点 const end fencePositions[(i 1) % fencePositions.length]; // 创建墙体几何体WallGeometry 自动拉伸成垂直面 const wallGeometry new Cesium.WallGeometry({ // 将两个经纬度点转换为笛卡尔坐标数组 positions: Cesium.Cartesian3.fromDegreesArray([ start.lon, start.lat, // 起点经度、纬度 end.lon, end.lat // 终点经度、纬度 ]), // 设置墙体顶部高度两个顶点高度相同 maximumHeights: [fenceHeight, fenceHeight], // 设置墙体底部高度贴地 minimumHeights: [0, 0], // 指定顶点包含位置和纹理坐标供 Shader 使用 vertexFormat: Cesium.VertexFormat.POSITION_AND_ST }); // 将几何体包装为可渲染的实例 const wallInstance new Cesium.GeometryInstance({ geometry: wallGeometry }); // 创建底层渲染对象Primitive性能优于 Entity const wallPrimitive new Cesium.Primitive({ // 绑定几何实例 geometryInstances: wallInstance, // 设置外观使用自定义呼吸材质 appearance: new Cesium.MaterialAppearance({ material: createPulseMaterial(), // 应用前面定义的脉冲材质 closed: false, // 不闭合墙体是开放面 flat: false, // 启用光照计算非纯平色 renderState: { // 启用 Alpha 混合实现半透明效果 blending: Cesium.BlendingState.ALPHA_BLEND, // 启用深度测试正确处理遮挡关系 depthTest: { enabled: true }, // 不写入深度缓冲避免遮挡其他物体 depthMask: false } }) }); // 将墙体添加到 3D 场景中 cesiumViewer.scene.primitives.add(wallPrimitive); }✅WallGeometry天然支持“墙”结构无需手动 extrude。4️⃣ 底部发光轮廓线Entity API为了简化开发我们使用EntityAPI 添加底部发光线。// 构建闭合折线的坐标数组格式lon, lat, height, ... const bottomLine []; // 遍历所有围栏点添加到底部线高度设为 0.1 米略高于地面 fencePositions.forEach(p bottomLine.push(p.lon, p.lat, 0.1)); // 闭合多边形将第一个点再次添加到末尾 bottomLine.push(fencePositions[0].lon, fencePositions[0].lat, 0.1); // 使用 Entity API 添加发光折线开发更简单 cesiumViewer.entities.add({ polyline: { // 将经纬度高度数组转换为 3D 笛卡尔坐标 positions: Cesium.Cartesian3.fromDegreesArrayHeights(bottomLine), // 线条宽度像素 width: 5, // 使用 Cesium 内置的发光材质 material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.4, // 发光强度值越大光晕越扩散 color: Cesium.Color.BLUE.withAlpha(0.7) // 蓝色70% 透明度 }) } });表格属性推荐值说明width3~8线条粗细glowPower0.2~0.6越大光晕越扩散5️⃣ 顶部闪烁警示点最后我们在每个角点上方添加一个动态闪烁的红色警示图标。// 定义一个红色圆形 SVG 图标Base64 编码内联使用无外部依赖 const imgBillboard data:image/svgxml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iOCIgc3Ryb2tlPSIjZmY2NjAwIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiNmZjY2MDAiIGZpbGwtb3BhY2l0eT0iMC44Ii8Cjwvc3ZnPgo; // 为每个围栏角点创建一个闪烁的图标 fencePositions.forEach((pos, idx) { cesiumViewer.entities.add({ // 设置图标位置在围栏顶部上方 5 米处 position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, fenceHeight 5), billboard: { image: imgBillboard, // 使用内联 SVG 图标 scale: 0.8, // 图标缩放比例 // 动态颜色每帧根据时间计算透明度 color: new Cesium.CallbackProperty(() { // 获取当前时间毫秒乘以系数控制闪烁速度 const t Date.now() * 0.003; // 计算透明度0.7 ~ 1.0 之间波动并加入相位偏移idx * 1.5 const intensity 0.7 0.3 * Math.sin(t idx * 1.5); // 返回红色透明度由 intensity 控制 return Cesium.Color.RED.withAlpha(intensity); }, false), // false 表示不缓存每帧重新计算 heightReference: Cesium.HeightReference.NONE // 使用绝对高度不贴地 } }); });✨ 每个点使用idx * 1.5实现错峰闪烁避免同步。 性能与兼容性对比方案渲染性能开发难度动画灵活性适用场景Primitive Custom Shader⭐⭐⭐⭐☆⭐⭐⭐☆⭐⭐⭐⭐⭐高性能、复杂动画Entity MaterialProperty⭐⭐⭐⭐⭐⭐⭐⭐快速原型、简单效果混合方案本文⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐推荐兼顾性能与开发效率 使用方式将上述逻辑封装为函数后使用非常简单// 1. 引入函数 import { loadPulseFence } from ./pulseFence.js; // 2. 初始化 Cesium Viewer const viewer new Cesium.Viewer(cesiumContainer); // 3. 调用函数加载呼吸围栏 loadPulseFence(viewer);✅ 支持多次调用绘制多个围栏。 自定义建议需求修改位置改变颜色修改baseColor和Cesium.Color调整高度修改fenceHeight改变闪烁频率调整Date.now() * 0.003中的系数增加更多点在fencePositions中添加坐标/** * 加载呼吸脉冲围栏效果墙体 底部发光 顶部闪烁点 * param {Cesium.Viewer} cesiumViewer - Cesium Viewer 实例 */ export const loadPulseFence (cesiumViewer) { // 定义围栏的四个角点坐标经纬度按顺时针或逆时针顺序排列 const fencePositions [ { lon: 116.397194, lat: 39.909768 }, { lon: 116.397194, lat: 39.900768 }, { lon: 116.392194, lat: 39.900768 }, { lon: 116.392194, lat: 39.909768 } ]; // 设置围栏墙体的高度单位米 const fenceHeight 60.0; /** * 创建呼吸脉冲材质用于墙体 * 使用自定义 GLSL Shader 实现正弦波动的透明度和自发光效果 * returns {Cesium.Material} 返回一个可复用的材质对象 */ const createPulseMaterial () { return new Cesium.Material({ // 定义材质的 fabric织物结构 fabric: { // 自定义材质类型名称任意命名用于内部识别 type: PulseFenceMaterial, // GLSL 片元着色器源码核心动画逻辑 source: // Cesium 内置函数根据输入生成默认材质 czm_material czm_getMaterial(czm_materialInput materialInput) { // 初始化一个默认材质 czm_material material czm_getDefaultMaterial(materialInput); // 获取当前帧号并乘以系数控制动画速度0.01 较慢 float time czm_frameNumber * 0.01; // 生成 [0, 1] 范围内的正弦波动值模拟“呼吸”节奏 float pulse 0.5 0.5 * sin(time); // 定义基础颜色深蓝色 (R0, G0.6, B1.0) vec3 baseColor vec3(0.0, 0.6, 1.0); // 透明度随 pulse 波动范围 [0.3, 0.9] float alpha 0.3 0.6 * pulse; // 设置漫反射颜色主色 material.diffuse baseColor; // 设置透明度必须配合 blending 才生效 material.alpha alpha; // 设置自发光强度随 pulse 增强最大为 baseColor * 1.8 material.emission baseColor * pulse * 1.8; // 返回最终材质 return material; } }, // 启用半透明渲染必须为 true否则 alpha 无效 translucent: true }); }; // 第一部分构建围栏墙体使用 Primitive 高性能渲染 // 遍历每个边从点 i 到点 i1 for (let i 0; i fencePositions.length; i) { // 获取当前起点 const start fencePositions[i]; // 获取下一个终点取模实现闭环最后一个点连回第一个点 const end fencePositions[(i 1) % fencePositions.length]; // 创建墙体几何体WallGeometry 天然支持垂直拉伸 const wallGeometry new Cesium.WallGeometry({ // 将经纬度数组转换为笛卡尔坐标数组格式[lon1, lat1, lon2, lat2] positions: Cesium.Cartesian3.fromDegreesArray([ start.lon, start.lat, end.lon, end.lat ]), // 设置每个顶点的最大高度墙体顶部高度 maximumHeights: [fenceHeight, fenceHeight], // 设置每个顶点的最小高度墙体底部高度0 表示贴地 minimumHeights: [0, 0], // 指定顶点属性格式包含位置和纹理坐标ST供 Shader 使用 vertexFormat: Cesium.VertexFormat.POSITION_AND_ST }); // 将几何体包装为 GeometryInstancePrimitive 的基本单元 const wallInstance new Cesium.GeometryInstance({ geometry: wallGeometry }); // 创建 Primitive底层渲染对象性能优于 Entity const wallPrimitive new Cesium.Primitive({ // 绑定几何实例 geometryInstances: wallInstance, // 设置外观使用自定义材质 appearance: new Cesium.MaterialAppearance({ // 应用呼吸脉冲材质 material: createPulseMaterial(), // 不闭合墙体是开放面 closed: false, // 使用 Phong 光照false 表示启用光照计算 flat: false, // 渲染状态配置 renderState: { // 启用 Alpha 混合实现半透明 blending: Cesium.BlendingState.ALPHA_BLEND, // 启用深度测试正确处理前后遮挡 depthTest: { enabled: true }, // 不写入深度缓冲避免遮挡其他物体 depthMask: false } }) }); // 将墙体添加到场景中 cesiumViewer.scene.primitives.add(wallPrimitive); } // 第二部分添加底部发光轮廓线使用 Entity 简化开发 // 构建闭合的折线坐标数组格式[lon1, lat1, height1, lon2, lat2, height2, ...] const bottomLine []; // 遍历所有围栏点添加到底部线 fencePositions.forEach(p bottomLine.push(p.lon, p.lat, 0.1)); // 闭合将第一个点再次添加到末尾 bottomLine.push(fencePositions[0].lon, fencePositions[0].lat, 0.1); // 使用 Entity API 添加发光折线 cesiumViewer.entities.add({ polyline: { // 将经纬度高度数组转换为笛卡尔坐标 positions: Cesium.Cartesian3.fromDegreesArrayHeights(bottomLine), // 线条宽度像素 width: 5, // 使用内置的发光材质 material: new Cesium.PolylineGlowMaterialProperty({ // 发光强度0.0~1.0值越大光晕越扩散 glowPower: 0.4, // 发光颜色蓝色带 70% 透明度 color: Cesium.Color.BLUE.withAlpha(0.7) }) } }); // 第三部分添加顶部闪烁警示点使用 SVG 图标 动态颜色 // 定义一个红色圆形 SVG 图标Base64 编码避免外部依赖 const imgBillboard data:image/svgxml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iOCIgc3Ryb2tlPSIjZmY2NjAwIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiNmZjY2MDAiIGZpbGwtb3BhY2l0eT0iMC44Ii8Cjwvc3ZnPgo; // 为每个围栏角点添加一个闪烁的 billboard图标 fencePositions.forEach((pos, idx) { cesiumViewer.entities.add({ // 设置图标位置在围栏顶部上方 5 米处 position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, fenceHeight 5), billboard: { // 使用内联 SVG 图标 image: imgBillboard, // 图标缩放比例 scale: 0.8, // 动态颜色使用 CallbackProperty 实现每帧更新 color: new Cesium.CallbackProperty(() { // 获取当前时间毫秒乘以系数控制闪烁速度 const t Date.now() * 0.003; // 计算透明度0.7 ~ 1.0 之间波动并加入相位偏移idx * 1.5使各点错峰闪烁 const intensity 0.7 0.3 * Math.sin(t idx * 1.5); // 返回红色透明度由 intensity 控制 return Cesium.Color.RED.withAlpha(intensity); }, false), // false 表示不缓存结果每帧都重新计算 // 高度参考NONE 表示使用绝对高度不贴地 heightReference: Cesium.HeightReference.NONE } }); }); // 打印日志确认加载完成 console.log(【呼吸脉冲围栏】加载完成Primitive 版本); };
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2424760.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!