大家好,我是日拱一卒的攻城师不浪,致力于技术与艺术的融合。这是2024年输出的第43/100篇文章。
前言
圆形扩散圈作为一种动态视觉效果,广泛应用于地理信息系统、军事模拟、游戏、环境监控、灾害预警等多个领域。
主要是为了展示某种现象、影响或事件的扩展过程,通过直观的视觉反馈帮助用户理解和分析影响范围、传播路径及强度。
在应急响应、决策支持等场景中,扩散圈都发挥着重要的作用。
今天我们就来看下在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(备注来意)。