ZDecode
Snippets

Cesium

天气特效

这只是个5毛钱特效🤑

基础特效抽象类

点击展开代码
BaseEffect.ts
import type { PostProcessStage, Viewer } from 'cesium'
import { Cartesian3 } from 'cesium'
const VISIBLE_DISTANCE = 80000
export abstract class BaseEffect {
  position: number[]
  viewer: Viewer
  effect: Nullable<PostProcessStage> = null
  _visible = true
  isDestroyed = false
  isAdd = false
  constructor(viewer: Viewer, position: number[]) {
    this.position = position
    this.viewer = viewer
    this.update()
  }

  get visible() {
    return this._visible
  }

  set visible(_visible) {
    this._visible = _visible
    if (this.effect) {
      this.effect.enabled = _visible
      if (_visible && !this.isAdd) {
        this.isAdd = true
        this.viewer.postProcessStages.add(this.effect)
      }
    }
  }
  abstract init(): void
  // 特效
  abstract createEffect(): PostProcessStage
  abstract createFragmentShader(): string

  update() {
    if (this.isDestroyed)
      return
    const position = Cartesian3.fromDegrees(this.position[0], this.position[1])
    const distance = Cartesian3.distance(this.viewer.scene.camera.position, position)
    requestAnimationFrame(() => {
      this.update()
    })
    this.visible = distance <= VISIBLE_DISTANCE
  }

  destroy() {
    if (!this.viewer || !this.effect)
      return
    this.viewer.scene.postProcessStages.remove(this.effect)
    this.isDestroyed = true
  }
}
BaseEffect.ts
import { Cartesian3 } from "cesium";
const VISIBLE_DISTANCE = 8e4;
export class BaseEffect {
	position;
	viewer;
	effect = null;
	_visible = true;
	isDestroyed = false;
	isAdd = false;
	constructor(viewer, position) {
		this.position = position;
		this.viewer = viewer;
		this.update();
	}
	get visible() {
		return this._visible;
	}
	set visible(_visible) {
		this._visible = _visible;
		if (this.effect) {
			this.effect.enabled = _visible;
			if (_visible && !this.isAdd) {
				this.isAdd = true;
				this.viewer.postProcessStages.add(this.effect);
			}
		}
	}
	update() {
		if (this.isDestroyed) return;
		const position = Cartesian3.fromDegrees(this.position[0], this.position[1]);
		const distance = Cartesian3.distance(this.viewer.scene.camera.position, position);
		requestAnimationFrame(() => {
			this.update();
		});
		this.visible = distance <= VISIBLE_DISTANCE;
	}
	destroy() {
		if (!this.viewer || !this.effect) return;
		this.viewer.scene.postProcessStages.remove(this.effect);
		this.isDestroyed = true;
	}
}

点击展开代码
RainEffect.ts
import type { Viewer } from 'cesium'
import { createUuid } from '@/utils'
import { defaultValue, PostProcessStage } from 'cesium'
import { BaseEffect } from './BaseEffect'
interface RainEffectOption {
  tiltAngle: number
  rainSize: number
  rainSpeed: number
}
export class RainEffect extends BaseEffect {
  tiltAngle: any
  rainSpeed: any
  rainSize: any
  constructor(
    viewer: Viewer,
    position: number[],
    options: RainEffectOption = { tiltAngle: -0.6, rainSize: 0.3, rainSpeed: 60.0 },
  ) {
    super(viewer, position)
    this.tiltAngle = defaultValue(options.tiltAngle, -0.6)
    this.rainSize = defaultValue(options.rainSize, 0.3)
    this.rainSpeed = defaultValue(options.rainSpeed, 60.0)
    this.init()
  }

  init() {
    this.effect = this.createEffect()
  }

  createEffect(): PostProcessStage {
    return new PostProcessStage({
      name: createUuid(),
      fragmentShader: this.createFragmentShader(),
      uniforms: {
        tiltAngle: () => {
          return this.tiltAngle
        },
        rainSize: () => {
          return this.rainSize
        },
        rainSpeed: () => {
          return this.rainSpeed
        },
      },
    })
  }

  createFragmentShader() {
    return `uniform sampler2D colorTexture;
          varying vec2 v_textureCoordinates;
          uniform float tiltAngle;
          uniform float rainSize;
          uniform float rainSpeed;
          float hash(float x) {
              return fract(sin(x * 133.3) * 13.13);
          }
          void main(void) {
              float time = czm_frameNumber / rainSpeed;
              vec2 resolution = czm_viewport.zw;
              vec2 uv = (gl_FragCoord.xy * 2. - resolution.xy) / min(resolution.x, resolution.y);
              vec3 c = vec3(.6, .7, .8);
              float a = tiltAngle;
              float si = sin(a), co = cos(a);
              uv *= mat2(co, -si, si, co);
              uv *= length(uv + vec2(0, 4.9)) * rainSize + 1.;
              float v = 1. - sin(hash(floor(uv.x * 100.)) * 2.);
              float b = clamp(abs(sin(20. * time * v + uv.y * (5. / (2. + v)))) - .95, 0., 1.) * 20.;
              c *= v * b;
              gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(c, 1), .5);
          }
          `
  }
}
RainEffect.ts
import { createUuid } from "@/utils";
import { defaultValue, PostProcessStage } from "cesium";
import { BaseEffect } from "./BaseEffect";
export class RainEffect extends BaseEffect {
	tiltAngle;
	rainSpeed;
	rainSize;
	constructor(viewer, position, options = {
		tiltAngle: -.6,
		rainSize: .3,
		rainSpeed: 60
	}) {
		super(viewer, position);
		this.tiltAngle = defaultValue(options.tiltAngle, -.6);
		this.rainSize = defaultValue(options.rainSize, .3);
		this.rainSpeed = defaultValue(options.rainSpeed, 60);
		this.init();
	}
	init() {
		this.effect = this.createEffect();
	}
	createEffect() {
		return new PostProcessStage({
			name: createUuid(),
			fragmentShader: this.createFragmentShader(),
			uniforms: {
				tiltAngle: () => {
					return this.tiltAngle;
				},
				rainSize: () => {
					return this.rainSize;
				},
				rainSpeed: () => {
					return this.rainSpeed;
				}
			}
		});
	}
	createFragmentShader() {
		return `uniform sampler2D colorTexture;
          varying vec2 v_textureCoordinates;
          uniform float tiltAngle;
          uniform float rainSize;
          uniform float rainSpeed;
          float hash(float x) {
              return fract(sin(x * 133.3) * 13.13);
          }
          void main(void) {
              float time = czm_frameNumber / rainSpeed;
              vec2 resolution = czm_viewport.zw;
              vec2 uv = (gl_FragCoord.xy * 2. - resolution.xy) / min(resolution.x, resolution.y);
              vec3 c = vec3(.6, .7, .8);
              float a = tiltAngle;
              float si = sin(a), co = cos(a);
              uv *= mat2(co, -si, si, co);
              uv *= length(uv + vec2(0, 4.9)) * rainSize + 1.;
              float v = 1. - sin(hash(floor(uv.x * 100.)) * 2.);
              float b = clamp(abs(sin(20. * time * v + uv.y * (5. / (2. + v)))) - .95, 0., 1.) * 20.;
              c *= v * b;
              gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(c, 1), .5);
          }
          `;
	}
}

点击展开代码
import type { Viewer } from 'cesium'
import { createUuid } from '@/utils'

import { defaultValue, PostProcessStage } from 'cesium'

import { BaseEffect } from './BaseEffect'

interface SnowEffectOptions {
  snowSize: number
  snowSpeed: number
}
export class SnowEffect extends BaseEffect {
  snowSize: 0.02
  snowSpeed: 60.0
  constructor(
    viewer: Viewer,
    position: number[],
    options: SnowEffectOptions = { snowSize: 0.02, snowSpeed: 60.0 },
  ) {
    super(viewer, position)
    this.snowSize = defaultValue(options.snowSize, 0.02) // 最好小于0.02
    this.snowSpeed = defaultValue(options.snowSpeed, 60.0)
    this.init()
  }

  init() {
    this.effect = this.createEffect()
  }

  createEffect(): PostProcessStage {
    return new PostProcessStage({
      name: createUuid(),
      fragmentShader: this.createFragmentShader(),
      uniforms: {
        snowSize: () => {
          return this.snowSize
        },
        snowSpeed: () => {
          return this.snowSpeed
        },
      },
    })
  }

  createFragmentShader() {
    return `uniform sampler2D colorTexture;
          varying vec2 v_textureCoordinates;
          uniform float snowSpeed;
          uniform float snowSize;
          float snow(vec2 uv,float scale)
          {
              float time=czm_frameNumber/snowSpeed;
              float w=smoothstep(1.,0.,-uv.y*(scale/10.));if(w<.1)return 0.;
              uv+=time/scale;uv.y+=time*2./scale;uv.x+=sin(uv.y+time*.5)/scale;
              uv*=scale;vec2 s=floor(uv),f=fract(uv),p;float k=3.,d;
              p=.5+.35*sin(11.*fract(sin((s+p+scale)*mat2(7,3,6,5))*5.))-f;d=length(p);k=min(d,k);
              k=smoothstep(0.,k,sin(f.x+f.y)*snowSize);
              return k*w;
          }
          void main(void){
              vec2 resolution=czm_viewport.zw;
              vec2 uv=(gl_FragCoord.xy*2.-resolution.xy)/min(resolution.x,resolution.y);
              vec3 finalColor=vec3(0);
              //float c=smoothstep(1.,0.3,clamp(uv.y*.3+.8,0.,.75));
              float c=0.;
              c+=snow(uv,30.)*.0;
              c+=snow(uv,20.)*.0;
              c+=snow(uv,15.)*.0;
              c+=snow(uv,10.);
              c+=snow(uv,8.);
              c+=snow(uv,6.);
              c+=snow(uv,5.);
              finalColor=(vec3(c));
              gl_FragColor=mix(texture2D(colorTexture,v_textureCoordinates),vec4(finalColor,1),.5);
              }
              `
  }
}
import { createUuid } from "@/utils";
import { defaultValue, PostProcessStage } from "cesium";
import { BaseEffect } from "./BaseEffect";
export class SnowEffect extends BaseEffect {
	snowSize;
	snowSpeed;
	constructor(viewer, position, options = {
		snowSize: .02,
		snowSpeed: 60
	}) {
		super(viewer, position);
		this.snowSize = defaultValue(options.snowSize, .02);
		this.snowSpeed = defaultValue(options.snowSpeed, 60);
		this.init();
	}
	init() {
		this.effect = this.createEffect();
	}
	createEffect() {
		return new PostProcessStage({
			name: createUuid(),
			fragmentShader: this.createFragmentShader(),
			uniforms: {
				snowSize: () => {
					return this.snowSize;
				},
				snowSpeed: () => {
					return this.snowSpeed;
				}
			}
		});
	}
	createFragmentShader() {
		return `uniform sampler2D colorTexture;
          varying vec2 v_textureCoordinates;
          uniform float snowSpeed;
          uniform float snowSize;
          float snow(vec2 uv,float scale)
          {
              float time=czm_frameNumber/snowSpeed;
              float w=smoothstep(1.,0.,-uv.y*(scale/10.));if(w<.1)return 0.;
              uv+=time/scale;uv.y+=time*2./scale;uv.x+=sin(uv.y+time*.5)/scale;
              uv*=scale;vec2 s=floor(uv),f=fract(uv),p;float k=3.,d;
              p=.5+.35*sin(11.*fract(sin((s+p+scale)*mat2(7,3,6,5))*5.))-f;d=length(p);k=min(d,k);
              k=smoothstep(0.,k,sin(f.x+f.y)*snowSize);
              return k*w;
          }
          void main(void){
              vec2 resolution=czm_viewport.zw;
              vec2 uv=(gl_FragCoord.xy*2.-resolution.xy)/min(resolution.x,resolution.y);
              vec3 finalColor=vec3(0);
              //float c=smoothstep(1.,0.3,clamp(uv.y*.3+.8,0.,.75));
              float c=0.;
              c+=snow(uv,30.)*.0;
              c+=snow(uv,20.)*.0;
              c+=snow(uv,15.)*.0;
              c+=snow(uv,10.);
              c+=snow(uv,8.);
              c+=snow(uv,6.);
              c+=snow(uv,5.);
              finalColor=(vec3(c));
              gl_FragColor=mix(texture2D(colorTexture,v_textureCoordinates),vec4(finalColor,1),.5);
              }
              `;
	}
}

点击展开代码
FogEffect.ts
import type { Viewer } from 'cesium'
import { createUuid } from '@/utils'

import { Color, defaultValue, PostProcessStage } from 'cesium'

import { BaseEffect } from './BaseEffect'
interface RainEffectOption {
  visibility: number
  color: Color
}
export class FogEffect extends BaseEffect {
  visibility: any
  color: any
  rainSize: any
  constructor(
    viewer: Viewer,
    position: number[],
    options: RainEffectOption = { visibility: 0.2, color: new Color(0.8, 0.8, 0.8, 0.5) },
  ) {
    super(viewer, position)
    this.visibility = defaultValue(options.visibility, 0.2)
    this.color = defaultValue(options.color, new Color(0.8, 0.8, 0.8, 0.5))
    this.init()
  }

  init() {
    this.effect = this.createEffect()
  }

  createEffect(): PostProcessStage {
    return new PostProcessStage({
      name: createUuid(),
      fragmentShader: this.createFragmentShader(),
      uniforms: {
        visibility: () => {
          return this.visibility
        },
        fogColor: () => {
          return this.color
        },
      },
    })
  }

  createFragmentShader() {
    return `uniform sampler2D colorTexture;
    uniform sampler2D depthTexture;
    uniform float visibility;
    uniform vec4 fogColor;
    varying vec2 v_textureCoordinates;
    void main(void)
    {
       vec4 origcolor = texture2D(colorTexture, v_textureCoordinates);
       float depth = czm_readDepth(depthTexture, v_textureCoordinates);
       vec4 depthcolor = texture2D(depthTexture, v_textureCoordinates);
       float f = visibility * (depthcolor.r - 0.3) / 0.2;
       if (f < 0.0) f = 0.0;
       else if (f > 1.0) f = 1.0;
       gl_FragColor = mix(origcolor, fogColor, f);
    }`
  }
}
FogEffect.ts
import { createUuid } from "@/utils";
import { Color, defaultValue, PostProcessStage } from "cesium";
import { BaseEffect } from "./BaseEffect";
export class FogEffect extends BaseEffect {
	visibility;
	color;
	rainSize;
	constructor(viewer, position, options = {
		visibility: .2,
		color: new Color(.8, .8, .8, .5)
	}) {
		super(viewer, position);
		this.visibility = defaultValue(options.visibility, .2);
		this.color = defaultValue(options.color, new Color(.8, .8, .8, .5));
		this.init();
	}
	init() {
		this.effect = this.createEffect();
	}
	createEffect() {
		return new PostProcessStage({
			name: createUuid(),
			fragmentShader: this.createFragmentShader(),
			uniforms: {
				visibility: () => {
					return this.visibility;
				},
				fogColor: () => {
					return this.color;
				}
			}
		});
	}
	createFragmentShader() {
		return `uniform sampler2D colorTexture;
    uniform sampler2D depthTexture;
    uniform float visibility;
    uniform vec4 fogColor;
    varying vec2 v_textureCoordinates;
    void main(void)
    {
       vec4 origcolor = texture2D(colorTexture, v_textureCoordinates);
       float depth = czm_readDepth(depthTexture, v_textureCoordinates);
       vec4 depthcolor = texture2D(depthTexture, v_textureCoordinates);
       float f = visibility * (depthcolor.r - 0.3) / 0.2;
       if (f < 0.0) f = 0.0;
       else if (f > 1.0) f = 1.0;
       gl_FragColor = mix(origcolor, fogColor, f);
    }`;
	}
}

天气控制类

点击展开代码
WeatherControl.ts
import type { WeatherType } from '@/api/model/stationModel'
import type Viewer from 'cesium/Source/Widgets/Viewer/Viewer'
import type { BaseEffect } from './effect'
import { FogEffect, RainEffect, SnowEffect } from './effect'

export class WeatherControl {
  position: number[]
  _weather: WeatherType = 'sun'
  weatherEffect: Nullable<BaseEffect> = null
  viewer: Viewer
  isDestroyed = false
  constructor(viewer: Viewer, position: number[], weather: WeatherType = 'sun') {
    this.viewer = viewer
    this.position = position
    this.weather = weather
  }

  get weather() {
    return this._weather
  }

  set weather(weather: WeatherType) {
    if (this._weather === weather)
      return
    this._weather = weather
    this.handleWeather()
  }

  handleWeather() {
    this.weatherEffect?.destroy()
    this.weatherEffect = null
    if (this.isDestroyed)
      return
    if (this.weather === 'rain') {
      this.weatherEffect = new RainEffect(this.viewer, this.position)
      return
    }
    if (this.weather === 'snow') {
      this.weatherEffect = new SnowEffect(this.viewer, this.position)
      return
    }
    if (this.weather === 'fog') {
      this.weatherEffect = new FogEffect(this.viewer, this.position)
    }
  }

  destroy() {
    if (this.weatherEffect)
      this.weatherEffect.destroy()
    this.isDestroyed = true
  }
}
WeatherControl.ts
import { FogEffect, RainEffect, SnowEffect } from "./effect";
export class WeatherControl {
	position;
	_weather = "sun";
	weatherEffect = null;
	viewer;
	isDestroyed = false;
	constructor(viewer, position, weather = "sun") {
		this.viewer = viewer;
		this.position = position;
		this.weather = weather;
	}
	get weather() {
		return this._weather;
	}
	set weather(weather) {
		if (this._weather === weather) return;
		this._weather = weather;
		this.handleWeather();
	}
	handleWeather() {
		this.weatherEffect?.destroy();
		this.weatherEffect = null;
		if (this.isDestroyed) return;
		if (this.weather === "rain") {
			this.weatherEffect = new RainEffect(this.viewer, this.position);
			return;
		}
		if (this.weather === "snow") {
			this.weatherEffect = new SnowEffect(this.viewer, this.position);
			return;
		}
		if (this.weather === "fog") {
			this.weatherEffect = new FogEffect(this.viewer, this.position);
		}
	}
	destroy() {
		if (this.weatherEffect) this.weatherEffect.destroy();
		this.isDestroyed = true;
	}
}