- 需求背景
- 解决效果
- Code
- index.vue
- fogEffect.ts
- rain.glsl
- snow.glsl
- fog.glsl
需求背景
需要实现天气模拟,日照模拟功能,提高三维实景效果
解决效果
Code
注意
:我以下glsl文件时基于 webgl1.0
,即cesium,创建球的时候,要指定 webgl 为 1.0 版本
index.vue
<- /**
* @author: liuk
* @date: 2024-07-15
* @describe: 地图环境
*/ -->
<template>
<div class="map_scene-wrap">
<div class="head_title_arrow">工具</div>
<div class="second-level-heading">
<span>天气模拟</span>
</div>
<div class="map_scene-content">
<div @click="weatherClick(0)" :class="{ select: weatherItemSelectIndex === 0 }">晴天</div>
<div @click="weatherClick(1)" :class="{ select: weatherItemSelectIndex === 1 }">下雨</div>
<div @click="weatherClick(2)" :class="{ select: weatherItemSelectIndex === 2 }">下雪</div>
<div @click="weatherClick(3)" :class="{ select: weatherItemSelectIndex === 3 }">大雾</div>
</div>
<div class="second-level-heading">
<span>日照模拟</span>
</div>
<div class="ymfxClass">
<div class="nowDate">{{ moment(new Date()).format('YYYY-MM-DD') }}</div>
<el-slider :marks="{0: '0点',24: '24点'}" v-model="hour" :min="0" :max="24" @input="shadowSliderChange"></el-slider>
</div>
</div>
</template>
<script lang="ts" setup>
import moment from "moment";
import {onMounted, reactive, toRefs} from "vue";
import {usemapStore} from "@/store/modules/cesiumMap";
import FogEffect from "./fogEffect.ts"
import rainGlsl from "./rain.glsl"
import snowGlsl from "./snow.glsl"
let lastStage, fogEffect
const mapStore = usemapStore()
const model = reactive({
weatherItemSelectIndex: -1,
hour: 12,
})
const {weatherItemSelectIndex, hour} = toRefs(model)
onMounted(() => {
fogEffect = new FogEffect({
show: false,
viewer,
maxHeight: 40000, //大于此高度后不显示
fogByDistance: new Cesium.Cartesian4(100, 0.0, 9000, 0.9),
color: Cesium.Color.WHITE,
});
fogEffect.show = false
})
const weatherClick = (index) => {
model.weatherItemSelectIndex = model.weatherItemSelectIndex === index ? -1 : index
removeStage()
switch (model.weatherItemSelectIndex) {
case 0:
model.hour = 12
shadowSliderChange(12)
break;
case 1:
showRain();
break;
case 2:
showSnow();
break;
case 3:
showfogEffect();
break;
}
}
// 地图逻辑
const viewer = mapStore.getCesiumViewer();
const showSnow = () => {
lastStage = viewer.scene.postProcessStages.add(new Cesium.PostProcessStage({fragmentShader: snowGlsl}));
}
const showRain = () => {
lastStage = viewer.scene.postProcessStages.add(new Cesium.PostProcessStage({fragmentShader: rainGlsl}));
}
const showfogEffect = () => {
fogEffect.show = true;
}
const shadowSliderChange = (val) => {
viewer.scene.globe.enableLighting = true
// JulianDate 与北京时间 相差8小时
const time = new Date(new Date().setHours(Number(val)) - 8 * 60 * 60 * 1e3);
time.setHours(val);
console.log(new Date(time).toLocaleString())
viewer.clock.currentTime = Cesium.JulianDate.fromIso8601(time.toISOString())// iso8601String
}
const removeStage = () => {
viewer.scene.postProcessStages.remove(lastStage);
fogEffect.show = false;
}
</script>
<style lang="scss" scoped>
.map_scene-wrap {
align-items: flex-start;
position: absolute;
top: 70px;
right: 65px;
width: 300px;
background: rgba(0, 0, 0, 0.6);
border-radius: 4px;
backdrop-filter: blur(2px);
padding: 20px;
.second-level-heading {
margin-left: 10px;
margin-top: 20px;
font-size: 14px;
color: #fff;
position: relative;
line-height: 1;
padding-left: 10px;
&::before {
position: absolute;
display: block;
content: '';
width: 3px;
height: 70%;
background-color: rgba(46, 165, 255, 1);
left: 0;
top: 50%;
transform: translateY(-50%);
}
i {
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
&:hover {
color: rgba(255, 255, 255, 1);
}
}
}
.map_scene-content {
display: flex;
flex-wrap: wrap;
font-family: PingFang SC Regular;
padding-top: 10px;
& > div {
padding: 4px 10px;
margin-right: 10px;
cursor: pointer;
color: #fff;
font-size: 13px;
background: rgba(46, 165, 255, 0.3);
border: 1px solid #2ea5ff;
&:not(:first-child) {
margin-left: 8px;
}
}
.select {
background: #2ea5ff;
border: 1px solid #2ea5ff;
}
}
.ymfxClass {
margin-left: 20px;
.nowDate {
text-align: right;
font-size: 12px;
font-family: SourceHanSansCN-Regular, SourceHanSansCN;
font-weight: 400;
color: #2ea5ff;
}
::v-deep .el-slider {
.el-slider__button {
width: 13px;
height: 13px;
position: relative;
top: -1px;
}
.el-slider__runway {
height: 4px;
background: rgb(255, 255, 255, 0.3);
.el-slider__bar {
height: 100%;
color: rgba(46, 165, 255, 1);
}
.el-slider__marks-text {
font-size: 12px;
font-weight: 400;
color: #2ea5ff;
}
}
.el-slider__stop {
display: none;
height: 4px;
background: rgb(255, 255, 255, 0.3);
}
}
}
}
</style>
fogEffect.ts
/**
* @author: liuk
* @date: 2024-07-14
* @describe:场景雾效果
* @by:根据深度图的深度值,对片元进行不同程度的模糊
*/
import * as Cesium from "cesium";
import FogFS from "./fog.glsl";
export default class FogEffect {
//========== 构造方法 ==========
constructor(options) {
this.viewer = options.viewer;
this.fogByDistance = Cesium.defaultValue(
options.fogByDistance,
new Cesium.Cartesian4(10, 0.0, 1000, 0.9)
); //雾强度
this.color = Cesium.defaultValue(options.color, Cesium.Color.WHITE); //雾颜色
this._show = Cesium.defaultValue(options.show, true);
this._maxHeight = Cesium.defaultValue(options.maxHeight, 9000);
this.init();
}
//========== 对外属性 ==========
//是否开启效果
get show() {
return this._show;
}
set show(val) {
this._show = Boolean(val);
this.FogStage.enabled = this._show;
}
//========== 方法 ==========
init() {
var that = this;
this.FogStage = new Cesium.PostProcessStage({
fragmentShader: FogFS,
uniforms: {
fogByDistance: function () {
return that.fogByDistance;
},
fogColor: function () {
return that.color;
}
},
enabled: this._show
});
this.viewer.scene.postProcessStages.add(this.FogStage);
//加控制,只在相机高度低于一定高度时才开启本效果
this.viewer.scene.camera.changed.addEventListener(this.camera_changedHandler, this);
}
camera_changedHandler(event) {
if (this.viewer.camera.positionCartographic.height < this._maxHeight) {
this.FogStage.enabled = this._show;
} else {
this.FogStage.enabled = false;
}
}
//销毁
destroy() {
this.viewer.scene.camera.changed.removeEventListener(this.camera_changedHandler, this);
this.viewer.scene.postProcessStages.remove(this.FogStage);
//删除所有绑定的数据
for (let i in this) {
delete this[i];
}
}
}
rain.glsl
uniform sampler2D colorTexture;//输入的场景渲染照片
varying vec2 v_textureCoordinates;
float hash(float x){
return fract(sin(x*133.3)*13.13);
}
void main(void){
float time = czm_frameNumber / 240.0;
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=-.4;
float si=sin(a),co=cos(a);
uv*=mat2(co,-si,si,co);
uv*=length(uv+vec2(0,4.9))*.3+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), 0.5); //将雨和三维场景融合
}
snow.glsl
uniform sampler2D colorTexture; //输入的场景渲染照片
varying vec2 v_textureCoordinates;
float snow(vec2 uv,float scale){
float time = czm_frameNumber / 60.0;
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)*0.01);
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 = 0.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), 0.5); //将雪和三维场景融合
}
fog.glsl
float getDistance(sampler2D depthTexture, vec2 texCoords)
{
float depth = czm_unpackDepth(texture2D(depthTexture, texCoords));
if (depth == 0.0) {
return czm_infinity;
}
vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth);
return -eyeCoordinate.z / eyeCoordinate.w;
}
float interpolateByDistance(vec4 nearFarScalar, float distance)
{
float startDistance = nearFarScalar.x;
float startValue = nearFarScalar.y;
float endDistance = nearFarScalar.z;
float endValue = nearFarScalar.w;
float t = clamp((distance - startDistance) / (endDistance - startDistance), 0.0, 1.0);
return mix(startValue, endValue, t);
}
vec4 alphaBlend(vec4 sourceColor, vec4 destinationColor)
{
return sourceColor * vec4(sourceColor.aaa, 1.0) + destinationColor * (1.0 - sourceColor.a);
}
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
uniform vec4 fogByDistance;
uniform vec4 fogColor;
varying vec2 v_textureCoordinates;
void main(void)
{
float distance = getDistance(depthTexture, v_textureCoordinates);
vec4 sceneColor = texture2D(colorTexture, v_textureCoordinates);
float blendAmount = interpolateByDistance(fogByDistance, distance);
vec4 finalFogColor = vec4(fogColor.rgb, fogColor.a * blendAmount);
gl_FragColor = alphaBlend(finalFogColor, sceneColor);
}