重磅开源!Cesium实现高度雾仿真,谁再说Cesium做不出好效果?

大家好,我是日拱一卒的攻城师不浪,致力于前沿科技探索,摸索小而美工作室,这是2025年输出的第43/100篇原创文章。

效果预览

www.bilibili.com/video/BV1vk...

Web3D的开发效果一直是人们所诟病的,如果想开发出一个比较逼真的仿真级别的特效场景,那就需要这个开发者在3D领域具有极高的开发能力以及经验。

特别是Cesium,众所周知,Cesium并不是以场景美观特效为主线,所以经常会有人吐槽,Cesium开发出来的效果很小儿科。

那么,今天,我们一起来探索一下在Cesium中实现一个非常逼真的效果------高度雾(Height Fog),让它为我们的效果提升一定的逼格!

什么是高度雾效果?

高度雾是一种基于高度的大气模拟效果,它模拟了现实世界中随着高度变化而产生的大气散射现象。

简单来说,就是低海拔区域会有更浓的雾气,而随着高度升高,雾气逐渐变淡直至消失。

这种效果不同于简单的距离雾(Distance Fog),高度雾会考虑地形起伏,使得山谷中雾气弥漫,而山顶则清晰可见,大大提升了场景的真实感和空间感。

应用场景

高度雾效果其实也不止可以应用到山谷间:

  1. 数字城市展示:在城市规划和展示中,添加高度雾可以模拟城市晨雾或霾天气,增强视觉层次感
  1. 气象模拟:模拟各种天气现象下的视觉效果,如谷地晨雾、山间云海等

  2. 旅游景区导览:增强景区三维展示的沉浸感和真实感

  3. 军事模拟:模拟特定天气条件下的视野和能见度

技术原理

高度雾效果的实现主要依赖于Cesium的**后期处理(Post Processing)**机制。这是一种在场景渲染完成后,对整个渲染结果进行二次处理的技术。主要有以下步骤:

  1. 场景深度获取 :通过深度纹理(Depth Texture)获取每个像素的深度信息

  2. 世界坐标计算:将屏幕坐标和深度信息转换为世界坐标

  3. 高度估算:计算每个像素点相对于地球表面的高度

  4. 雾密度计算:基于高度差异计算雾的密度

  5. 颜色混合:将原始场景颜色与雾色按计算出的密度进行混合

整个过程通过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;
}

这是核心算法,计算雾的密度。它考虑了以下因素:

  1. 视线方向与垂直方向的夹角

  2. 相机高度与像素高度的差异

  3. 雾的最大高度与全局密度

  4. 特殊情况处理(如水平视线)

最终通过一个非线性映射,将计算结果限制在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(备注来意)。

相关推荐
青山Coding11 天前
Cesium基础(五)实体创建与拖拽
前端·cesium
Mapmost14 天前
全新升级!3DTiles加载速度Mapmost完胜Cesium
性能优化·webgl·cesium
不浪brown14 天前
告别静态水面的平庸!Cesium中实现水面倒影+动态流向的逼真水特效
cesium
汪洪墩15 天前
使用Mars3d加载热力图的时候,出现阴影碎片
开发语言·前端·javascript·vue.js·cesium
springfe010116 天前
Cesium 3D地图 图元 圆柱 图片实现
前端·cesium
不浪brown18 天前
浏览器3D渲染卡成PPT?6个性能优化指标,你都知道吗?
three.js·cesium
GIS之家24 天前
vue+cesium示例:3D热力图(附源码下载)
前端·vue.js·3d·cesium·webgis·3d热力图
不浪brown25 天前
开源!矢量建筑白模泛光特效以及全国77个大中城市的矢量shp数据获取!
前端·cesium
在下胡三汉1 个月前
怎么解决cesium加载模型太黑,程序崩溃,不显示,位置不对模型太大,Cesium加载gltf/glb模型后变暗
3dmax·cesium