前言:
本文将带你从零实现一套可调节浓度、动态流动、真实晚霞色调的火烧云效果,基于 Cesium 原生 PostProcessStage 后处理技术,纯着色器实现、性能高、无第三方插件。
演示动态图效果如下:

效果亮点:
- 真实晚霞色调:橙红、金橙渐变模拟日落火烧云
- 分层云层流动:多层 FBM 分形噪声叠加,云朵动态缓慢流动
- 参数可调节:滑块实时控制火烧云浓烈程度
核心技术原理:
Cesium 后处理机制
Cesium 提供了 PostProcessStage 屏幕后处理阶段,允许我们在场景渲染完成后、屏幕输出前,通过自定义片元着色器对整张屏幕纹理进行二次加工。气象、滤镜、泛光、调色、天气效果,全部都是基于该原理实现。
火烧云效果核心原理
单纯的纯色渐变无法模拟真实云朵,真实云层是不规则、细碎、多层次的纹理。
本案例采用行业标准的 FBM 分形 布朗运动 噪声:
- 多层噪声叠加,模拟云朵疏密变化
- 不同UV缩放+时间偏移,实现云层流动动画
- 通过 Y 轴遮罩,只作用于天空区域
- 橙红+金黄双色混合,模拟日落晚霞色温
完整可运行源码:
以下为 Vue3 + Cesium 完整可直接运行代码,包含 UI 控制、参数调节。
1. template 结构:
xml
<template>
<div class="main">
<!-- Cesium地球渲染容器 -->
<div class="content" ref="content" id="earth"></div>
<div class="btn-border" v-if="isLoading">
<div class="slider-border" v-if="isFireCloud">
<div class="slider-label">火烧云浓烈程度:{{ Number(fireCloudStrength).toFixed(2) }}</div>
<el-slider
class="firecloud-slider"
v-model.number="fireCloudStrength"
:min="0"
:max="0.8"
:step="0.01"
@input="updateFireCloudStrength"
/>
</div>
<!-- 镜头复位按钮,回到预设相机视角 -->
<el-button type="primary" size="default" class="btn" @click="flyTo">初始位置</el-button>
<!-- 火烧云开关按钮,切换开启/关闭状态 -->
<el-button type="primary" size="default" class="btn" @click="fireCloudControl">
{{ isFireCloud ? '关闭火烧云' : '开启火烧云' }}
</el-button>
</div>
<!-- 地图加载提示,初始化未完成时显示 -->
<div class="loading" v-if="!isLoading">Loading...</div>
</div>
</template>
2. script 代码:
ini
<script setup>
import { onMounted, nextTick, ref, onUnmounted } from 'vue';
import { token } from '../../utils/common.js';
// 地图是否初始化加载完成
let isLoading = ref(false);
// 火烧云效果开关状态
let isFireCloud = ref(false);
// 火烧云浓烈程度,取值范围 0 ~ 0.8
let fireCloudStrength = ref(0.8);
// 地图加载延时定时器句柄
let myMar = null;
/**
* 组件销毁生命周期钩子
* 销毁Cesium实例、后处理效果,释放GPU资源,避免内存泄漏
*/
onUnmounted(() => {
// 销毁火烧云后处理阶段
if (isFireCloud.value && window.fireCloudStage) {
window.viewer.scene.postProcessStages.remove(window.fireCloudStage);
window.fireCloudStage = null;
isFireCloud.value = false;
}
// 销毁Cesium Viewer实例
if (window.viewer) {
window.viewer.destroy();
window.viewer = null;
}
// 清除延时定时器
if (myMar) {
clearTimeout(myMar);
myMar = null;
}
});
/**
* DOM挂载完成钩子
* nextTick等待DOM渲染完毕之后再初始化地图实例
*/
onMounted(() => {
nextTick(() => {
initMap();
});
});
/**
* 初始化Cesium Viewer地图实例
*/
const initMap = async () => {
// 您的Cesium Ion token
Cesium.Ion.defaultAccessToken = token;
// 设置相机默认视口矩形范围
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.5, 20.4, 110.4, 61.2);
// 加载Cesium世界地形,开启水面蒙版、地形法线,用于水面效果与光照
const terrainProvider = await Cesium.createWorldTerrainAsync({
requestWaterMask: true,
requestVertexNormals: true
});
// 创建Viewer实例,关闭全部多余的UI控件
window.viewer = new Cesium.Viewer('earth', {
terrainProvider: terrainProvider,
animation: false,
timeline: false,
infoBox: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
baseLayerPicker: false,
navigationHelpButton: false,
fullscreenButton: false,
selectionIndicator: false,
shouldAnimate: false,
contextOptions: {
webgl: {
powerPreference: "high-performance",
preserveDrawingBuffer: false
}
}
});
// 设置场景时间(仅赋值,不驱动时钟)
Cesium.JulianDate.fromDate(new Date('2026/05/02 23:00:00'));
// 延时3秒,标记地图加载完成,显示操作按钮,并且飞到预设初始视角
myMar = setTimeout(() => {
isLoading.value = true;
flyTo();
}, 3000);
};
/**
* 镜头飞到预设初始位置
*/
const flyTo = () => {
window.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(117.66293312354773, 26.00085216052459, 1796.8781247739746),
orientation: {
heading: Cesium.Math.toRadians(38.280907385928664),
pitch: Cesium.Math.toRadians(-4.0671391165843245),
roll: Cesium.Math.toRadians(0.0009439239381974838)
},
duration: 6
});
};
/**
* 更新火烧云强度uniform,滑块拖动实时更新着色器参数
* @param {Number} val 0‑0.8 火烧云浓度值
*/
const updateFireCloudStrength = (val) => {
if (window.fireCloudStage) {
window.fireCloudStage.uniforms.fireCloudStrength = Number(val);
}
};
/**
* 火烧云效果开关
* 开启:创建PostProcessStage后处理,加载片元着色器,加入后处理管线
* 关闭:移除后处理阶段,销毁实例
*/
const fireCloudControl = () => {
if (isFireCloud.value) {
// 关闭火烧云,移除后处理阶段
window.viewer.scene.postProcessStages.remove(window.fireCloudStage);
window.fireCloudStage = null;
isFireCloud.value = false;
} else {
// 火烧云片元着色器,Cesium PostProcessStage专用GLSL‑ES300语法
const FireCloudShader = `
uniform sampler2D colorTexture; // 场景原始渲染纹理
uniform float fireCloudStrength; // 火烧云强度 0‑0.8
uniform float time; // 时间,用于云层流动动画
in vec2 v_textureCoordinates; // 屏幕纹理uv坐标
out vec4 fragColor; // 输出片元颜色
// 基础随机噪声函数
float noise(vec2 uv){
return fract(sin(dot(uv,vec2(12.9898,78.233)))*43758.5453);
}
// 平滑插值噪声
float smoothNoise(vec2 uv){
vec2 i = floor(uv);
vec2 f = fract(uv);
float a = noise(i);
float b = noise(i + vec2(1.,0.));
float c = noise(i + vec2(0.,1.));
float d = noise(i + vec2(1.,1.));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
// 分形布朗运动,多层噪声叠加,生成云纹理
float fbm(vec2 uv){
float total = 0.0;
float amp = 0.52;
for(int i = 0; i < 6; i++){
total += smoothNoise(uv) * amp;
uv *= 2.2;
amp *= 0.46;
}
return total;
}
void main(void){
// 获取原始场景颜色
vec4 origin = texture(colorTexture, v_textureCoordinates);
vec3 originRGB = origin.rgb;
// skyMask:遮罩,只在地平线天空区域渲染火烧云,地面不生效
float skyMask = smoothstep(0.05, 0.75, v_textureCoordinates.y);
// 多套uv,不同缩放+时间偏移,实现多层云层流动
vec2 cloudUV = v_textureCoordinates *1.7 + vec2(time * 0.00035, time * 0.00012);
float cloudNoise = fbm(cloudUV);
float cloudDensity = smoothstep(0.1,0.92,cloudNoise);
vec2 cloudUV2 = v_textureCoordinates *3.1 + vec2(time *0.0006,-time*0.0002);
float cloudNoise2 = fbm(cloudUV2);
float cloudDensity2 = smoothstep(0.12,0.9,cloudNoise2);
vec2 cloudUV3 = v_textureCoordinates *4.8 + vec2(time*0.0009, time*0.00015);
float cloudNoise3 = fbm(cloudUV3);
float cloudDensity3 = smoothstep(0.14,0.88,cloudNoise3);
// 合并多层云密度
float finalDensity = max(cloudDensity, max(cloudDensity2*0.72, cloudDensity3*0.55));
// 火烧云渐变色调:橙红过渡到金橙
vec3 fireColorA = vec3(1.0,0.42,0.18);
vec3 fireColorB = vec3(1.0,0.72,0.25);
vec3 fireCloudColor = mix(fireColorA,fireColorB,finalDensity);
// 计算最终混合透明度
float alpha = finalDensity * skyMask * fireCloudStrength;
// 原始画面和火烧云颜色做混合
vec3 finalColor = mix(originRGB, mix(originRGB,fireCloudColor,0.72), alpha);
fragColor = vec4(finalColor, origin.a);
}
`;
// 创建后处理阶段实例
window.fireCloudStage = new Cesium.PostProcessStage({
name: 'fire_cloud_effect',
fragmentShader: FireCloudShader,
uniforms: {
fireCloudStrength: fireCloudStrength.value,
time: 0.0
}
});
// requestAnimationFrame驱动time,实现云层流动动画
const tick = () => {
if (window.fireCloudStage) {
window.fireCloudStage.uniforms.time += 0.016;
requestAnimationFrame(tick);
}
};
tick();
// 将后处理加入渲染管线
window.viewer.scene.postProcessStages.add(window.fireCloudStage);
isFireCloud.value = true;
}
};
</script>
3. css样式代码:
css
* {
margin: 0;
padding: 0;
}
.main {
width: 100%;
height: 100vh;
position: relative;
}
.content {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
}
.btn-border {
position: absolute;
right: 24px;
top: 24px;
z-index: 2;
display: flex;
justify-content: start;
align-items: stretch;
}
.slider-border {
width: 260px;
margin-right: 20px;
position: relative;
top: -9px;
}
.slider-label {
font-size: 14px;
}
.btn {
margin-left: 20px;
cursor: pointer;
}
.loading {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 3;
display: flex;
justify-content: center;
align-items: center;
font-size: 34px;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
color: #000000;
}
代码核心讲解:
多层 FBM 云层算法
通过六层噪声叠加,让云朵纹理细节丰富、层次分明,不会出现生硬的色块。配合多组不同缩放、不同运动方向的UV,实现真实无序云层流动效果。
天空区域遮罩 skyMask
通过纹理 Y 轴坐标做平滑遮罩,让火烧云只渲染在天空、地平线区域,完全不会覆盖地面建筑、地形,效果非常自然。
双色渐变晚霞
采用「橙红 + 金橙」冷暖渐变配色,根据云层密度自动插值颜色,完美模拟日落时分的晚霞色温变化。
总结:
本文完整实现了Cesium 动态流动火烧云晚霞的效果,后续我会在本 Cesium 专栏中持续输出更多的天气效果的实战案例。