Cesium中绘制圆形扩散圈,为应急辅助决策提供强有力支撑!

1,335 阅读4分钟

大家好,我是日拱一卒的攻城师不浪,致力于技术与艺术的融合。这是2024年输出的第43/100篇文章。

前言

圆形扩散圈作为一种动态视觉效果,广泛应用于地理信息系统军事模拟游戏环境监控灾害预警等多个领域。

22.webp

主要是为了展示某种现象、影响或事件的扩展过程,通过直观的视觉反馈帮助用户理解和分析影响范围传播路径强度

在应急响应、决策支持等场景中,扩散圈都发挥着重要的作用。

今天我们就来看下在Cesium中如何绘制一个圆形扩散圈,主要是通过自定义的后处理阶段(PostProcessStage)和着色器(Shader)来实现。

封装扩散圈大类

我们封装一个类CircleDiffusion,提供对扩散效果的管理,包括添加清除更新效果。

import * as Cesium from "cesium";

// 圆扩散
export default class CircleDiffusion {
  viewer;
  lastStageList;

  constructor(viewer) {
    this.viewer = viewer;
    this.lastStageList = [];
  }

  add(position, scanColor, maxRadius, duration) {
    this.lastStageList.push(
      this.showCircleScan(position, scanColor, maxRadius, duration)
    );
  }

  clear() {
    this.lastStageList.forEach((ele) => {
      this.clearScanEffects(ele);
    });
    this.lastStageList = [];
  }
}

添加圆形扩散效果

add方法用于在指定位置添加扩散波效果,它接受四个参数

  • 扫描中心位置(position

  • 扫描颜色(scanColor

  • 最大扫描半径(maxRadius

  • 扫描的持续时间(duration

这个方法会调用showCircleScan来显示扩散效果。

showCircleScan(position, scanColor, maxRadius, duration) {
  const cartographicCenter = new Cesium.Cartographic(
    Cesium.Math.toRadians(position[0]),
    Cesium.Math.toRadians(position[1]),
    position[2]
  );
  scanColor = new Cesium.Color.fromCssColorString(scanColor);
  const lastStage = this._addCircleScanPostStage(
    cartographicCenter,
    maxRadius,
    scanColor,
    duration
  );
  return lastStage;
}

showCircleScan方法中,我们需要先将地理坐标转换为Cartesian坐标系的中心点,然后计算扫描颜色。

再调用_addCircleScanPostStage来创建一个后处理阶段,实际上是将扩散效果通过自定义的Shader应用到Cesium场景中。

创建扩散效果的后处理阶段

_addCircleScanPostStage方法创建了一个PostProcessStage,该阶段会使用自定义的着色器来渲染扩散效果。

_addCircleScanPostStage(cartographicCenter, maxRadius, scanColor, duration) {
  const _Cartesian3Center = Cesium.Cartographic.toCartesian(cartographicCenter);
  const _Cartesian4Center = new Cesium.Cartesian4(
    _Cartesian3Center.x,
    _Cartesian3Center.y,
    _Cartesian3Center.z,
    1
  );

  // 扫描中心点偏移量
  const _CartograhpicCenter1 = new Cesium.Cartographic(
    cartographicCenter.longitude,
    cartographicCenter.latitude,
    cartographicCenter.height + 500
  );
  const _Cartesian3Center1 = Cesium.Cartographic.toCartesian(_CartograhpicCenter1);

  const _time = new Date().getTime();
  const _scratchCartesian4Center = new Cesium.Cartesian4();
  const _scratchCartesian4Center1 = new Cesium.Cartesian4();
  const _scratchCartesian3Normal = new Cesium.Cartesian3();
  const _this = this;

  // 创建后处理阶段
  const ScanPostStage = new Cesium.PostProcessStage({
    fragmentShader: _this._getScanSegmentShader(),
    uniforms: {
      u_scanCenterEC: function () {
        const temp = Cesium.Matrix4.multiplyByVector(
          _this.viewer.camera._viewMatrix,
          _Cartesian4Center,
          _scratchCartesian4Center
        );
        return temp;
      },
      u_scanPlaneNormalEC: function () {
        const temp = Cesium.Matrix4.multiplyByVector(
          _this.viewer.camera._viewMatrix,
          _Cartesian4Center,
          _scratchCartesian4Center
        );
        const temp1 = Cesium.Matrix4.multiplyByVector(
          _this.viewer.camera._viewMatrix,
          _Cartesian4Center1,
          _scratchCartesian4Center1
        );
        _scratchCartesian3Normal.x = temp1.x - temp.x;
        _scratchCartesian3Normal.y = temp1.y - temp.y;
        _scratchCartesian3Normal.z = temp1.z - temp.z;
        Cesium.Cartesian3.normalize(
          _scratchCartesian3Normal,
          _scratchCartesian3Normal
        );
        return _scratchCartesian3Normal;
      },
      u_radius: function () {
        return (
          (maxRadius * ((new Date().getTime() - _time) % duration)) / duration
        );
      },
      u_scanColor: scanColor,
    },
  });

  this.viewer.scene.postProcessStages.add(ScanPostStage);
  return ScanPostStage;
}

这里我们通过PostProcessStage将扩散效果作为后处理阶段应用到场景中。它包含了一个着色器,该着色器计算每个像素是否处于扩散波的范围内,并根据距离来调整颜色。

扩散效果Shader

扩散效果主要是自定义片段着色器(fragmentShader)。在着色器中,计算每个像素到扫描中心的距离,并根据距离动态调整颜色,形成扩散波的效果:

_getScanSegmentShader() {
  const inpram = 18; // 控制扩散的强度
  const scanSegmentShader =
    ` 
    uniform sampler2D colorTexture;
    uniform sampler2D depthTexture;
    in vec2 v_textureCoordinates;
    uniform vec4 u_scanCenterEC;
    uniform vec3 u_scanPlaneNormalEC;
    uniform float u_radius;
    uniform vec4 u_scanColor;
    out vec4 fragColor;

    // 计算深度信息
    float getDepth(in vec4 depth){
        float z_window = czm_unpackDepth(depth);
        z_window = czm_reverseLogDepth(z_window);
        return z_window;
    }

    void main(){
      fragColor = texture(colorTexture, v_textureCoordinates);
      float depth = getDepth(texture(depthTexture, v_textureCoordinates));
      vec4 viewPos = toEye(v_textureCoordinates, depth);
      vec3 prjOnPlane = pointProjectOnPlane(u_scanPlaneNormalEC.xyz, u_scanCenterEC.xyz, viewPos.xyz);
      float dis = length(prjOnPlane.xyz - u_scanCenterEC.xyz);
      if(dis < u_radius){
        float f = 1.0 - abs(u_radius - dis) / u_radius;
        f = pow(f, float(` + inpram + `));
        fragColor = mix(fragColor,u_scanColor,f);
      }
      fragColor.a = fragColor.a / 2.0;
    }`;
  return scanSegmentShader;
}

通过pointProjectOnPlane函数计算每个像素点到扫描中心的距离,并根据这个距离调整颜色,模拟扩散波的效果。

清除扩散效果

创建clear方法清除所有扩散效果,并将后处理阶段从场景中移除。

clearScanEffects(lastStage) {
  this.viewer.scene.postProcessStages.remove(lastStage);
}

最后

扩散圈的效果非常适合用在需要动态爆炸地震等扩散类效果的场景,完整代码我放在了开源项目中:【github.com/tingyuxuan2…

如果认为有帮助请给予我们一个star支持鼓励我们开源更多!

有需要进可视化&Webgis交流群可以加我:brown_7778(备注来意)。