大家好,我是日拱一卒的
攻城师不浪
,致力于前沿科技探索,摸索小而美工作室,这是2025年输出的第43/100篇原创文章。
效果预览
www.bilibili.com/video/BV1vk...
Web3D的开发效果一直是人们所诟病的,如果想开发出一个比较逼真的仿真级别的特效场景,那就需要这个开发者在3D领域具有极高的开发能力以及经验。
特别是Cesium,众所周知,Cesium并不是以场景美观特效为主线,所以经常会有人吐槽,Cesium开发出来的效果很小儿科。
那么,今天,我们一起来探索一下在Cesium中实现一个非常逼真的效果------高度雾(Height Fog)
,让它为我们的效果提升一定的逼格!
什么是高度雾效果?
高度雾是一种基于高度的大气模拟效果,它模拟了现实世界中随着高度变化而产生的大气散射现象。
简单来说,就是低海拔区域会有更浓的雾气,而随着高度升高,雾气逐渐变淡直至消失。

这种效果不同于简单的距离雾(Distance Fog)
,高度雾会考虑地形起伏,使得山谷中雾气弥漫,而山顶则清晰可见,大大提升了场景的真实感和空间感。
应用场景
高度雾效果其实也不止可以应用到山谷间:
- 数字城市展示:在城市规划和展示中,添加高度雾可以模拟城市晨雾或霾天气,增强视觉层次感

-
气象模拟:模拟各种天气现象下的视觉效果,如谷地晨雾、山间云海等
-
旅游景区导览:增强景区三维展示的沉浸感和真实感
-
军事模拟:模拟特定天气条件下的视野和能见度
技术原理
高度雾效果的实现主要依赖于Cesium的**后期处理(Post Processing)**机制。这是一种在场景渲染完成后,对整个渲染结果进行二次处理的技术。主要有以下步骤:
-
场景深度获取 :通过
深度纹理(Depth Texture)
获取每个像素的深度信息 -
世界坐标计算:将屏幕坐标和深度信息转换为世界坐标
-
高度估算:计算每个像素点相对于地球表面的高度
-
雾密度计算:基于高度差异计算雾的密度
-
颜色混合:将原始场景颜色与雾色按计算出的密度进行混合
整个过程通过GLSL
着色器(Fragment Shader)在GPU上高效执行,不会对性能造成明显影响。
代码解析
让我们来看看核心的代码实现:
1. 后期处理阶段创建
javascript
const create = () => {
customPostProcessStage = new Cesium.PostProcessStage({
fragmentShader: fs,
uniforms: {
u_earthRadiusOnCamera: () => Cesium.Cartesian3.magnitude(__viewer.camera.positionWC) - __viewer.camera.positionCartographic.height,
u_cameraHeight: () => __viewer.camera.positionCartographic.height,
u_fogColor: () => new Cesium.Color(fogParams.fogColor.r / 255, fogParams.fogColor.g / 255, fogParams.fogColor.b / 255),
u_fogHeight: () => fogParams.fogHeight,
u_globalDensity: () => fogParams.globalDensity,
}
})
__viewer.scene.postProcessStages.add(customPostProcessStage)
fogParams.enabled = true;
// 初始化GUI
initGUI();
}
这段代码创建了一个PostProcessStage
对象,它是Cesium后期处理的核心。我们传入了自定义的片元着色器fs
,并定义了一系列uniforms
变量,这些变量会在着色器执行过程中被实时更新:
-
u_earthRadiusOnCamera
: 相机位置处的地球半径 -
u_cameraHeight
: 相机高度 -
u_fogColor
: 雾的颜色 -
u_fogHeight
: 雾的最大高度 -
u_globalDensity
: 雾的全局密度
2. 着色器核心算法
世界坐标获取
glsl
vec4 getWorldCoordinate(sampler2D depthTexture, vec2 texCoords) {
float depthOrLogDepth = czm_unpackDepth(texture(depthTexture, texCoords));
vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, depthOrLogDepth);
eyeCoordinate = eyeCoordinate / eyeCoordinate.w;
vec4 worldCoordinate = czm_inverseView * eyeCoordinate;
worldCoordinate = worldCoordinate / worldCoordinate.w;
return worldCoordinate;
}
这个函数从深度纹理中提取深度值,然后通过一系列坐标变换,将屏幕坐标转换为世界坐标。这是实现后期处理效果的基础。
高度计算
glsl
float getRoughHeight(vec4 worldCoordinate) {
float disToCenter = length(vec3(worldCoordinate));
return disToCenter - u_earthRadiusOnCamera;
}
这个函数计算了像素点到地球中心的距离,减去地球半径,得到粗略的高度值。这种方法计算量极小,适合实时渲染。
线性高度雾计算
glsl
float linearHeightFog(vec3 positionToCamera, float cameraHeight, float pixelHeight, float fogMaxHeight) {
float globalDensity = u_globalDensity / 10.0;
vec3 up = -1.0 * normalize(czm_viewerPositionWC);
float vh = projectVector(normalize(positionToCamera), up);
// 让相机沿着视线方向移动 雾气产生距离 的距离
float s = step(100.0, length(positionToCamera));
vec3 sub = mix(positionToCamera, normalize(positionToCamera) * 100.0, s);
positionToCamera -= sub;
cameraHeight = mix(pixelHeight, cameraHeight - 100.0 * vh, s);
float b = mix(cameraHeight, fogMaxHeight, step(fogMaxHeight, cameraHeight));
float a = mix(pixelHeight, fogMaxHeight, step(fogMaxHeight, pixelHeight));
float fog = (b - a) - 0.5 * (pow(b, 2.0) - pow(a, 2.0)) / fogMaxHeight;
fog = globalDensity * fog / vh;
if(abs(vh) <= 0.01 && cameraHeight < fogMaxHeight) {
float disToCamera = length(positionToCamera);
fog = globalDensity * (1.0 - cameraHeight / fogMaxHeight) * disToCamera;
}
fog = mix(0.0, 1.0, fog / (fog + 1.0));
return fog;
}
这是核心算法,计算雾的密度。它考虑了以下因素:
-
视线方向与垂直方向的夹角
-
相机高度与像素高度的差异
-
雾的最大高度与全局密度
-
特殊情况处理(如水平视线)
最终通过一个非线性映射,将计算结果限制在0-1
范围内,作为雾的最终强度。
主函数
glsl
void main(void) {
vec4 color = texture(colorTexture, v_textureCoordinates);
vec4 positionWC = getWorldCoordinate(depthTexture, v_textureCoordinates);
float pixelHeight = getRoughHeight(positionWC);
vec3 positionToCamera = vec3(vec3(positionWC) - czm_viewerPositionWC);
float fog = linearHeightFog(positionToCamera, u_cameraHeight, pixelHeight, u_fogHeight);
out_FragColor = mix(color, vec4(u_fogColor, 1.0), fog);
}
主函数流程清晰:获取原始颜色,计算世界坐标和高度,计算雾的强度,最后将原始颜色与雾色混合。
mix
函数根据第三个参数(雾强度)在两种颜色之间进行线性插值。
最后
【开源地址】:github.com/jiawanlong/...
【Cesium开源集合】:github.com/tingyuxuan2...
想系统学习Cesium的小伙伴儿,可以了解下不浪的教程
《Cesium从入门到实战》
,将Cesium的知识点进行串联,让不了解Cesium的小伙伴拥有一个完整的学习路线,并最终完成一个智慧城市
的完整项目,课程最近也更新了很多进阶内容,想了解课程大纲,+作者:brown_7778(备注来意)。
有需要进可视化&Webgis交流群
可以加我:brown_7778(备注来意)。