前言
在前面的实战中,我们已经实现了:
- Cesium 黄昏浓度自定义调节(昼夜光影柔化、傍晚氛围)
- Cesium 雾气浓度可控特效(远近雾化、大气朦胧感)
今天我们继续沿用 Cesium PostProcessStage 后处理着色器 方案,手写一套超高质感、可调节、可开关、真实星空闪烁 的夜景效果。
本案例中Cesium版本为1.141.0
演示动态图效果如下:

演示静态图效果如下:

实现效果:
1. 场景智能分区星空:
通过纹理坐标 Y 轴遮罩计算,严格区分天空区域和地面区域。
只在画面上半部分的天空生成星点,山脉、地面完全不会出现星星,完全贴合现实物理逻辑。
2. 自然星星呼吸闪烁动画:
基于 sin(time) 正弦周期函数,让每一颗星星拥有独立随机明暗节奏。
星星不是统一闪烁,而是零零散散、忽明忽暗、微弱呼吸。
3. 可交互式夜景浓度调节
延续我专栏统一的参数化可控思想(和之前两篇的雾效、黄昏特效逻辑一致)。
通过滑块实时修改 nightStrength 数值,可以:
- 微调夜空昏暗程度
- 控制星星整体亮度浓度
- 实现「微夜色 → 深夜纯黑」无缝过渡
4. 冷蓝色深夜夜空基调:
普通夜景只是简单压暗画面,整体发灰、发黑,没有夜空质感。
本次效果叠加了低饱和冷蓝夜空底色,模拟真实深夜大气色调,画面通透、深邃、不发死,夜景层次感极强。
核心实现原理极简梳理:
- 画面压暗:通过 nightStrength 系数整体降低画面亮度,构建夜晚基础环境。
- 天空遮罩裁切:使用 smoothstep 平滑过渡,精准锁定天空渲染区域。
- 随机星点生成:数学随机函数生成高密度、均匀分布的星点位置。
- 时间动态插值:逐帧更新时间变量,实现星星呼吸闪烁。
- 夜空色调混合:叠加冷蓝底色,提升夜景通透质感。
完整代码
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="isNightSky">
<div class="slider-label" style="color:#FFFFFF;">夜景昏暗程度:{{ Number(nightStrength).toFixed(2) }}</div>
<!-- 夜景强度滑块容器,用于控制夜景强度,仅开启星空夜景效果时有效 -->
<el-slider
class="night-slider"
v-model.number="nightStrength"
:min="0"
:max="0.8"
:step="0.01"
@input="updateNightStrength"
/>
</div>
<!-- 镜头复位按钮:一键飞回到初始视角 -->
<el-button type="primary" size="default" class="btn" @click="flyTo">初始位置</el-button>
<!-- 星空夜景开关按钮,切换开启/关闭状态变量isNightSky,根据isNightSky状态动态修改按钮文字 -->
<el-button type="primary" size="default" class="btn" @click="nightControl">
{{ isNightSky ? '关闭星空夜景' : '开启星空夜景' }}
</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 isNightSky = ref(false);
// 夜景昏暗强度默认值0.8,滑块范围0~0.8
const nightStrength = ref(0.8);
// 地图加载完成标识 false=加载中 true=加载完毕
const isLoading = ref(false);
let myMar = null;
onUnmounted(() => {
// 销毁星空后处理渲染管线
if (isNightSky.value && window.nightStage) {
window.viewer.scene.postProcessStages.remove(window.nightStage);
window.nightStage = null;
isNightSky.value = false;
}
// 销毁Cesium三维场景实例,释放WebGL、瓦片资源
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 23:00:00'));
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 // 飞行时长6秒
});
};
// 滑块拖动实时更新夜景昏暗强度
const updateNightStrength = (val) => {
if (window.nightStage) { // 校验夜景处理实例是否存在
// 将滑块数值传递给着色器uniform变量,实时更新画面夜晚浓度
window.nightStage.uniforms.nightStrength = Number(val);
}
};
/**
* 星空夜景开关核心逻辑
* 关闭:移除后处理管线、销毁着色器实例
* 开启:创建星空片元着色器,注册到场景后处理渲染管线
*/
const nightControl = () => {
if (isNightSky.value) {
// 关闭星空夜景:从渲染管线移除夜景滤镜,释放WebGL资源
window.viewer.scene.postProcessStages.remove(window.nightStage);
window.nightStage = null;
isNightSky.value = false;
} else {
const NightSkyShader = `
uniform sampler2D colorTexture;
uniform float nightStrength;
uniform float time;
in vec2 v_textureCoordinates;
out vec4 fragColor;
// 随机噪声函数,生成星星点位
float random(vec2 uv) {
return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);
}
void main(void) {
vec4 originColor = texture(colorTexture, v_textureCoordinates);
vec3 rawRGB = originColor.rgb;
// 全局夜晚压暗系数
float darkRate = 1.0 - nightStrength * 0.65;
vec3 nightBase = rawRGB * darkRate;
// 仅在画面上半部分(天空)生成星星
float skyMask = smoothstep(0.18, 1.0, v_textureCoordinates.y);
vec2 starUV = v_textureCoordinates * 1200.0;
float randVal = random(floor(starUV));
// 星星阈值:只保留少量随机白点作为星星
float starMask = step(0.997, randVal);
// 正弦时间实现明暗闪烁
float twinkle = sin(time * 3.2 + randVal * 20.0) * 0.45 + 0.55;
vec3 starColor = vec3(1.0, 1.0, 1.0) * starMask * twinkle * skyMask * nightStrength;
// 夜空冷蓝底色混合
vec3 nightTint = vec3(0.04, 0.06, 0.12);
vec3 mixNight = mix(nightBase, nightTint, nightStrength * 0.38);
vec3 finalRGB = mixNight + starColor;
fragColor = vec4(finalRGB, originColor.a);
}
`;
// 实例化后处理渲染阶段,挂载夜景着色器与全局变量
window.nightStage = new Cesium.PostProcessStage({
name: 'night_star_effect', // 后处理唯一标识名称
fragmentShader: NightSkyShader, // 绑定星空片元着色器
uniforms: {
nightStrength: nightStrength.value, // 初始化夜晚强度数值
time: 0.0 // 动画时间初始值
}
});
/**
* 逐帧动画循环,持续更新time变量驱动星星闪烁
* 关闭夜景后自动停止循环,节省性能
*/
const tick = () => {
if (window.nightStage) {
window.nightStage.uniforms.time += 0.016;
requestAnimationFrame(tick);
}
};
tick();
// 将星空夜景滤镜加入场景后处理管线,立即生效
window.viewer.scene.postProcessStages.add(window.nightStage);
// 标记夜景状态为开启
isNightSky.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;
}
总结:
本文通过原生着色器算法,真正模拟了物理级别的夜空表现:分区天空、动态闪烁、可控浓度、色调真实。