Cesium实现动态流动火烧云晚霞效果(可用slider调整火烧云浓度)

前言:

本文将带你从零实现一套可调节浓度、动态流动、真实晚霞色调的火烧云效果,基于 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 专栏中持续输出更多的天气效果的实战案例。

相关推荐
码视野1 小时前
基于 Vue3 + Element Plus 的【青少年心理健康智能测评与 AI 情绪树洞陪护干预系统】设计与实现(含PRD/三端源码/大屏)
前端·vue.js·人工智能·vue3
daols882 小时前
vue 表格vxe-table 实现紧凑型表格的方式
前端·javascript·vue.js
Java搬码工2 小时前
VUE3使用教程
前端·javascript·vue.js
小白勇闯网安圈2 小时前
Vue 3 核心进阶:组合式 API、响应式系统与 <script setup>
前端·javascript·vue.js
IT爱学堂2 小时前
Three.js可视化企业实战WEBGL课,2023年全新WEB 3D THREEJS技术
前端·javascript·webgl
Su米苏3 小时前
关于@vue-office/excel 渲染异常滚动失去内容
前端·vue.js·excel
zhifou12345617 小时前
Vue基础(二)
前端·javascript·vue.js
你脑门上的脚印19 小时前
Vue 项目从零实现语音转文字、文字转语音功能(完整可用 + 踩坑指南)
前端·vue.js
lemon_yyds21 小时前
H5 页面嵌入 APP 的 web-view 时-调用原生能力(uniapp)
vue.js