这其实是一个很简单能实现的效果,百度搜索随便一搜会出现很多博客。

为什么还要再出一个?想在这里把我找到的实现此效果的方法及策略来一个综合对比。

个人觉得自己的实现方式相对来说性能会好一些。代码量相对少很多。

所有给出的代码直接粘贴到cesium官方沙盒示例里面就可以运行。

第一种实现方式:

百度搜索出来的,80%源代码粘贴过来的。大致看了下采用后处理方式渲染,必须开启深度检测。

代码中直接添加了1000个实体,很卡,可视化效果也一般般。

const viewer = new Cesium.Viewer("cesiumContainer");

viewer.scene.globe.depthTestAgainstTerrain = true;

var entity = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(111.0, 40.0, 1500),
    billboard: {
        image: "../images/Cesium_Logo_overlay.png",
        scale: 1,
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
    }
});
viewer.flyTo(entity);

for (var i = 0; i < 1000; i++) {
    var cartographicCenter = Cesium.Cartographic.fromDegrees(110 + Math.random() * 1,
        40 + Math.random() * 1,
        0);
    var scanColor = new Cesium.Color(0.0, 1.0, 0.0, 1);
    var lastStage = addCircleScanPostStage(viewer, cartographicCenter, 1000, scanColor, 4000);
}

//扩散效果Shader
function getScanSegmentShader() {
    var scanSegmentShader = "\n\
uniform sampler2D colorTexture;\n\
uniform sampler2D depthTexture;\n\
in vec2 v_textureCoordinates;\n\
uniform vec4 u_scanCenterEC;\n\
uniform vec3 u_scanPlaneNormalEC;\n\
uniform float u_radius;\n\
uniform vec4 u_scanColor;\n\
\n\
vec4 toEye(in vec2 uv,in float depth)\n\
{\n\
vec2 xy = vec2((uv.x * 2.0 - 1.0),(uv.y * 2.0 - 1.0));\n\
vec4 posIncamera = czm_inverseProjection * vec4(xy,depth,1.0);\n\
posIncamera = posIncamera/posIncamera.w;\n\
return posIncamera;\n\
}\n\
\n\
vec3 pointProjectOnPlane(in vec3 planeNormal,in vec3 planeOrigin,in vec3 point)\n\
{\n\
vec3 v01 = point - planeOrigin;\n\
float d = dot(planeNormal,v01);\n\
return (point-planeNormal * d);\n\
}\n\
float getDepth(in vec4 depth)\n\
{\n\
float z_window = czm_unpackDepth(depth);\n\
z_window = czm_reverseLogDepth(z_window);\n\
float n_range = czm_depthRange.near;\n\
float f_range = czm_depthRange.far;\n\
return (2.0 * z_window - n_range - f_range)/(f_range-n_range);\n\
} \n\
void main()\n\
{\n\
out_FragColor = texture(colorTexture,v_textureCoordinates);\n\
float depth = getDepth(texture(depthTexture,v_textureCoordinates));\n\
vec4 viewPos = toEye(v_textureCoordinates,depth);\n\
vec3 prjOnPlane = pointProjectOnPlane(u_scanPlaneNormalEC.xyz,u_scanCenterEC.xyz,viewPos.xyz);\n\
float dis = length(prjOnPlane.xyz - u_scanCenterEC.xyz);\n\
if(dis<u_radius)\n\ {\n\ float f=1.0-abs(u_radius - dis )/ u_radius;\n\ f=pow(f,4.0);\n\
out_FragColor=mix(out_FragColor,u_scanColor,f);\n\ }\n\ } \n\ ";
    return scanSegmentShader;
}

/*
添加扩散效果扫描线
viewer
cartographicCenter 扫描中心
radius 半径 米
scanColor 扫描颜色
duration 持续时间 毫秒
*/
function addCircleScanPostStage(viewer, cartographicCenter, maxRadius, scanColor, duration) {
    var _Cartesian3Center = Cesium.Cartographic.toCartesian(cartographicCenter);
    var _Cartesian4Center = new Cesium.Cartesian4(_Cartesian3Center.x, _Cartesian3Center.y, _Cartesian3Center.z, 1);

    var _CartograhpicCenter1 = new Cesium.Cartographic(cartographicCenter.longitude, cartographicCenter.latitude, cartographicCenter.height + 500);
    var _Cartesian3Center1 = Cesium.Cartographic.toCartesian(_CartograhpicCenter1);
    var _Cartesian4Center1 = new Cesium.Cartesian4(_Cartesian3Center1.x, _Cartesian3Center1.y, _Cartesian3Center1.z, 1);

    var _time = (new Date()).getTime();

    var _scratchCartesian4Center = new Cesium.Cartesian4();
    var _scratchCartesian4Center1 = new Cesium.Cartesian4();
    var _scratchCartesian3Normal = new Cesium.Cartesian3();


    var ScanPostStage = new Cesium.PostProcessStage({
        fragmentShader: getScanSegmentShader(),
        uniforms: {
            u_scanCenterEC: function () {
                var temp = Cesium.Matrix4.multiplyByVector(viewer.camera._viewMatrix, _Cartesian4Center, _scratchCartesian4Center);
                return temp;
            },
            u_scanPlaneNormalEC: function () {
                var temp = Cesium.Matrix4.multiplyByVector(viewer.camera._viewMatrix, _Cartesian4Center, _scratchCartesian4Center);
                var temp1 = Cesium.Matrix4.multiplyByVector(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
        }
    });

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

 

第二种方式:和第三种方式一样,扩展材质属性,唯一的区别在shder不一样,替换第三种方式的shader就可以看到效果。和第三种方式相比区别在不能支持多个波同时扩散。

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    material.diffuse = 1.5 * color.rgb;

    vec2 st = materialInput.st;
    float dis = distance(st, vec2(0.5, 0.5));

    float per = fract(time);
    if(dis > per * 0.5){
       //material.alpha = 0.0;
       discard;
    }else {
         material.alpha = color.a  * dis / per / 2.0;
    }
    return material;
}

  

  

第三种方式:这种方式在网上应该有类似的实现方案,就是扩展一个MaterialProperty,其中着色器代码自己加工修改过,能够自己自定义扩散圆数量,算法逻辑就不详细介绍,有需要的直接粘贴过去使用即可。循环最大值是写死的10,默认最大只支持9个圆扩散。这种方式加载1000个性能是没有影响的,就相当于加载了普通的1000个圆形实例。

const viewer = new Cesium.Viewer("cesiumContainer");
let Color = Cesium.Color;
let defaultValue = Cesium.defaultValue;
let defined = Cesium.defined;
let Event = Cesium.Event;
let createPropertyDescriptor = Cesium.createPropertyDescriptor;
let Property = Cesium.Property;
let Material = Cesium.Material;

var defaultColor = Color.WHITE;

function ElliposidWaveMaterialProperty(options) {
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);

    this._definitionChanged = new Event();
    this._color = undefined;
    this._colorSubscription = undefined;

    this.color = options.color;
    this.count = options.count || 1;
    this.duration = Cesium.defaultValue(options.duration, 1e3);
    this.gradient = Cesium.defaultValue(options.gradient, 0.1);
    this._time = performance.now();

}

Object.defineProperties(ElliposidWaveMaterialProperty.prototype, {
    isConstant: {
        get: function () {
            return false;
        }
    },
    definitionChanged: {
        get: function () {
            return this._definitionChanged;
        }
    },
    color: createPropertyDescriptor("color")
});

ElliposidWaveMaterialProperty.prototype.getType = function (time) {
    return Material.EllipsoidWaveType;
};

ElliposidWaveMaterialProperty.prototype.getValue = function (time, result) {
    if (!defined(result)) {
        result = {};
    }
    result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);

    if (this._time === undefined) {
        this._time = time.secondsOfDay;
    }
    result.time = (performance.now() - this._time) / this.duration;
    result.count = this.count;
    result.gradient = 1 + 10 * (1 - this.gradient);
    return result;
};

ElliposidWaveMaterialProperty.prototype.equals = function (other) {
    return this === other || //
        (other instanceof ElliposidWaveMaterialProperty &&
            Property.equals(this._color, other._color));
};

Material.EllipsoidWaveType = "EllipsoidWave";
const ElliposidWaveMaterial = "czm_material czm_getMaterial(czm_materialInput materialInput) { \n\
    czm_material material = czm_getDefaultMaterial(materialInput); \n\
    material.diffuse = 1.5 * color.rgb; \n\
    vec2 st = materialInput.st; \n\
    float dis = distance(st, vec2(0.5, 0.5)); \n\
    float per = fract(time); \n\
    if (dis > 0.5) {  \n\
        discard;  \n\
    } else {  \n\
        float perDis = 0.5 / count; \n\
        float disNum; \n\
        float bl = .0; \n\
        for (int i = 0; i < 10; i++) {\n\
            if(float(i) > count) {\n\
                break;\n\
            }   \n\
            disNum = perDis * float(i) - dis + per / count; \n\
            if (disNum > 0.0) {  \n\
                if (disNum < perDis) {  \n\
                    bl = 1.0 - disNum / perDis; \n\
                } \n\
                else if(disNum - perDis < perDis) { \n\
                    bl = 1.0 - abs(1.0 - disNum / perDis);  \n\
                }  \n\
                material.alpha = pow(bl, gradient);  \n\
            }  \n\
        }  \n\
    }  \n\
    return material;  \n\
}";

Material._materialCache.addMaterial(Material.EllipsoidWaveType, {
    fabric: {
        type: Material.EllipsoidWaveType,
        uniforms: {
            color: new Color(1, 0, 0, 1.0),
            time: 1,
            count: 1,
            duration: 1000,
            gradient: 0.1
        },
        source: ElliposidWaveMaterial
    },
    translucent: function () {
        return true;
    }
});

for (var i = 0; i < 1000; i++) {
    let greenCircle = viewer.entities.add({
        position: Cesium.Cartesian3.fromDegrees(110 + Math.random() * 1,
        40 + Math.random() * 1,
        10),
        name: "Green circle at height with outline",
        ellipse: {
            semiMinorAxis:1000.0,
            semiMajorAxis: 1000.0,
            height: 10.0,
            material: new ElliposidWaveMaterialProperty({
                color: Color.GREEN,
                count: 3,
                duration: 3000,
                gradient: 0.2,
            })
        },
    });
}

viewer.zoomTo(viewer.entities);