Cesium实现黄昏效果:按钮一键控制打开/关闭黄昏效果,滑块拖动实时控制黄昏浓烈度

前言

之前给大家分享过如何实现Cesium下雪效果和Cesium雾气效果,都是通过后处理、粒子系统实现三维场景的天气氛围仿真。 今天继续分享Cesium天气特效系列 ------ 自定义黄昏日落氛围效果

同样采用 PostProcessStage 后处理着色器 实现,支持:

  • 一键开启 / 关闭黄昏效果。
  • Slider 滑块实时调节黄昏浓烈程度。
  • 远景分层暖雾渐变,画面更有日落层次感。

可以直接用于数字孪生、三维场景氛围美化、环境仿真场景。

演示动态图示如下:

演示地址如下:

3d.xiazhi.tech/3d-demos/#/...

效果特点:

  • 区别于简单的全局变色,采用屏幕竖向分层渲染,天空远景黄昏暖色更浓,近景地形建筑更自然。
  • 通过 uniform 对外暴露强度参数,支持滑块实时动态调节。

实现思路:

通过 Cesium 后处理管线,自定义片元着色器:

  • 对原始场景纹理进行采样。
  • 整体压暗模拟傍晚光线减弱。
  • 叠加暖橙日落色调。
  • 根据屏幕纵向坐标实现远景分层暖雾效果
  • 外部绑定滑块数值,动态控制黄昏混合强度。

完整代码

1. template 结构:

xml 复制代码
<template>
    <div class="main">

        <!-- 地球容器 -->
        <div class="content" ref="content" id="earth"></div>

        <div class="btn-border" v-if="isLoading">

            <div class="slider-border" v-if="isDusk">
                <div class="slider-label">黄昏浓烈程度:{{ Number(duskStrength).toFixed(2) }}</div>

                <!-- 黄昏浓度滑块容器,用于控制黄昏浓烈程度,仅开启黄昏效果时有效 -->
                <el-slider
                  class="dusk-slider"
                  v-model.number="duskStrength"
                  :min="0"
                  :max="0.4"
                  :step="0.01"
                  @input="updateDuskStrength"
                />
            </div>

            <!-- 镜头复位按钮:一键飞回到初始视角 -->
            <el-button type="primary" size="default" class="btn" @click="flyTo">初始位置</el-button>

            <!-- 黄昏效果开关按钮:切换开启/关闭状态变量isDusk,根据isDusk状态动态修改按钮文字 -->
            <el-button type="primary" size="default" class="btn" @click="duskControl">
                {{ isDusk ? '关闭黄昏效果' : '开启黄昏效果' }}
            </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';

// 黄昏效果全局开关标识 true=开启,false=关闭
const isDusk = ref(false);

// 黄昏浓烈程度默认值0.4,滑块范围0~0.4
const duskStrength = ref(0.4);

// 地图加载完成标识 false=加载中 true=加载完毕
const isLoading = ref(false);

let myMar = null;

onUnmounted(() => {
	
    // 若黄昏后处理实例存在,移除并销毁
    if (isDusk.value && window.duskStage) {
        window.viewer.scene.postProcessStages.remove(window.duskStage);
        window.duskStage = null;
        isDusk.value = false;
    }

    // 销毁 Cesium Viewer 实例,释放资源
    if (window.viewer) {
        window.viewer.destroy();
        window.viewer = null;
    }

    if (myMar) {
        clearTimeout(myMar);
        myMar = null;
    }
});

// 组件挂载后:初始化地图
onMounted(() => {
    nextTick(() => {
        initMap();
    });
});

// 初始化 Cesium 地图的方法
const initMap = async () => {
	
    // 设置 Cesium Ion 的token(这里替换成您的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 实例
    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 上下文配置
            webgl: {
                powerPreference: "high-performance",  // 高性能模式
                preserveDrawingBuffer: false  // 不保留绘图缓冲(节省内存)
            }
        }
    });
	
    // 设置模拟时间(可选,用于光影效果)
    Cesium.JulianDate.fromDate(new Date('2026/05/02 17:30:00'));

    myMar = setTimeout(() => {
        isLoading.value = true;
        flyTo();
    }, 3000);
	
};


// 飞往初始视角
const flyTo = () => {
    window.viewer.camera.flyTo({
        destination: Cesium.Cartesian3.fromDegrees(117.7028548405434, 26.046540824114697, 1341.492305543057),
        orientation: {
            heading: Cesium.Math.toRadians(38.298412435906),  // 航向角
            pitch: Cesium.Math.toRadians(-4.009058145409306),  // 俯仰角
            roll: Cesium.Math.toRadians(0.0010907956242872084)  // 翻滚角
        },
        duration: 6  // 飞行时长6秒
    });
};

// 滑块拖动实时更新黄昏强度的方法
const updateDuskStrength = (val) => {
    if (window.duskStage) {  // 校验黄昏处理实例是否存在
      // 将滑块数值传递给着色器uniform变量,实时更新画面黄昏浓度
      window.duskStage.uniforms.duskStrength = Number(val);
    }
};

/**
 * 黄昏效果开关核心方法
 * 关闭:移除后处理管线、销毁着色器实例
 * 开启:创建黄昏片元着色器,注册到场景后处理渲染管线
 */
const duskControl = () => {
    if (isDusk.value) {  // 当黄昏为开启状态时,执行关闭黄昏的逻辑
        // 从渲染管线移除黄昏滤镜,释放WebGL资源
        window.viewer.scene.postProcessStages.remove(window.duskStage);
        window.duskStage = null;
        isDusk.value = false;
    } else {
        // 黄昏效果片元着色器代码
        const DuskShader = `
            // Cesium后处理内置完整场景纹理
            uniform sampler2D colorTexture;
            // 外部滑块传入黄昏浓烈程度 0~0.4
            uniform float duskStrength;
            // 当前像素屏幕纹理坐标 [0,1]
            in vec2 v_textureCoordinates;
            // 输出叠加黄昏后的像素RGBA
            out vec4 fragColor;

            void main(void) {
                // 采样原始场景画面
                vec4 originColor = texture(colorTexture, v_textureCoordinates);
                vec3 rawRGB = originColor.rgb;

                // 黄昏基础色:暖橙红 日落色调
                vec3 duskWarmColor = vec3(0.98, 0.72, 0.42);
                // 黄昏压暗系数,模拟傍晚光线减弱
                float darkFactor = 1.0 - duskStrength * 0.3;

                // 垂直分层:画面顶部远景黄昏雾更重
                float screenY = v_textureCoordinates.y;
                float warmFogFactor = smoothstep(0.15, 1.0, screenY) * duskStrength * 0.6;

                // 第一步:原图整体压暗,模拟傍晚弱光
                vec3 darkBase = rawRGB * darkFactor;
                // 第二步:叠加暖橙黄昏底色
                vec3 mixWarm = mix(darkBase, duskWarmColor, duskStrength * 0.45);
                // 第三步:远景叠加更浓的暖色薄雾,强化日落纵深感
                vec3 finalRGB = mix(mixWarm, duskWarmColor, warmFogFactor);

                // 保留原图透明度输出
                fragColor = vec4(finalRGB, originColor.a);
            }
        `;

        // 实例化后处理渲染阶段,挂载黄昏着色器与强度变量
        window.duskStage = new Cesium.PostProcessStage({
            name: 'dusk_warm_effect',  // 后处理唯一标识名称
            fragmentShader: DuskShader,  // 绑定黄昏片元着色器
            uniforms: {
                duskStrength: duskStrength.value  // 初始化强度数值
            }
        });

        // 将黄昏滤镜加入场景后处理管线,立即生效
        window.viewer.scene.postProcessStages.add(window.duskStage);

        // 标记黄昏状态为开启
        isDusk.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;
}

.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;
}

.btn-border {
    position: absolute;
    right: 24px;
    top: 24px;
    z-index: 2;
    display: flex;
    justify-content: start;
    align-items: stretch;
}

.btn {
    margin-left: 20px;
    cursor: pointer;
}

.slider-border {
    width: 260px;
    margin-right: 20px;
    position: relative;
    top: -9px;
}

.slider-label {
    font-size: 14px;
}

核心逻辑说明

1. 分层黄昏原理

本文采用 屏幕 Y 轴分层混合

  • 画面上方(天空):暖色雾效更浓。
  • 画面下方(地形建筑):保留更多原始细节。

2. 动态强度调节

通过 duskStrength 统一控制:

  • 画面压暗程度。
  • 暖色调混合比例。
  • 远景暖色雾浓度。

拖动滑块即可实时预览不同程度的黄昏氛围。

相关推荐
用户2136610035721 小时前
Vue2脚手架工程化与Axios集成
前端·vue.js
Cobyte2 小时前
21.Vue Vapor 组件的实现原理
前端·javascript·vue.js
橙某人3 小时前
LogicFlow 工作流撤销与重做:从「全量快照」到「命令模式」🎯
前端·vue.js
ZhengEnCi13 小时前
Q04-Vite禁用CSS代码分割-解决生产环境样式加载顺序混乱问题
前端·vue.js·vite
青山Coding1 天前
Cesium应用(八):物体运动的实现思路
前端·cesium
用户2136610035721 天前
Vue2非父子通信与动态组件
前端·vue.js
如果超人不会飞2 天前
脉络清晰的业务演进:TinyVue Timeline 时间线组件全方位实战指南
vue.js
如果超人不会飞2 天前
从扁平到立体:掌握 TinyVue Grid 树形表格的高级实战指南
vue.js
用户2136610035722 天前
Vue2组件化开发与父子通信
前端·vue.js