重磅开源!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(备注来意)。

相关推荐
gis_rc2 天前
python下shp转3dtiles
python·3d·cesium·3dtiles·数字孪生模型
grasperp3 天前
3DTiles数据切片工具,支持LAS、OBJ、FBX 3DTiles怎么切片?3DTiles切片
cesium·3dtiles·三维gis·3dtiles切片·数据切片
duansamve5 天前
Cesium中实现在地图上移动/旋转点、线、面
cesium
冥界摄政王6 天前
CesiumJS学习第四章 替换指定3D建筑模型
3d·vue·html·webgl·js·cesium
冥界摄政王8 天前
Cesium学习第二章 camera 相机
node.js·html·vue3·js·cesium
冥界摄政王9 天前
Cesium学习第一章 安装下载 基于vue3引入Cesium项目开发
vue·vue3·html5·webgl·cesium
你们瞎搞10 天前
Cesium加载20GB航测影像.tif
前端·cesium·gdal·地图切片
闲云一鹤12 天前
Cesium 使用 Turf 实现坐标点移动(偏移)
前端·gis·cesium
二狗哈12 天前
Cesium快速入门34:3dTile高级样式设置
前端·javascript·算法·3d·webgl·cesium·地图可视化
二狗哈13 天前
Cesium快速入门33:tile3d设置样式
3d·状态模式·webgl·cesium·地图可视化