我用AI把高质量的体积云搬进了Cesium

我用AI把高质量的体积云搬进了Cesium
SEAlencehe前言
作为一名webGIS开发者,CesiumJS简陋的场景特效一直被诟病,想要实现类似于UE5那样的高质量体积云和大气散射,在AI时代纯靠自己手搓不现实。
我前前后后经历了多轮迭代,自认为算是成功移植takram-design-engineering/three-geospatial的场景特效,性能和视觉效果也达到了平衡
- 体积云;
- 大气散射;
- 地表云影;
- 体积光;
- 透镜炫光(Lens Flare);
- 云层运动和时间重建
先看效果
日落
云底
云上
体积光
透镜炫光
一些基础概念
Ray Marching
体积云没有固定的几何表面,不能像普通模型那样直接画三角形。
程序需要从相机发射一条射线,然后沿着射线一步一步向前检查:
第 1 步:这里有没有云?
第 2 步:这里的云有多浓?
第 3 步:太阳能照到这里吗?
第 4 步:累计颜色和透明度
……
这种方式叫 Ray Marching,可以翻译为“射线步进”。
步数越多:
- 云越细腻;
- 光照越准确;
- 性能消耗越高。
所以体积云的开发始终是在画质和性能之间寻找平衡。
HDR 和 Tone Mapping
现实中的太阳非常亮,但普通屏幕只能显示有限的亮度。
HDR 渲染允许程序先保存超出屏幕范围的亮度,比如:
普通地面亮度:1
白云亮度:5
太阳亮度:100
最后再通过 Tone Mapping,把这些亮度压缩到屏幕可以显示的范围。
可以把 Tone Mapping 理解成相机拍照后的“显影过程”。
一个重要原则是:
云、大气、太阳和 Lens Flare 应该先在 HDR 中合成,最后只进行一次 Tone Mapping。
如果中途已经把颜色处理过一次,最后 Cesium 又处理一次,就可能出现:
- 场景发白;
- 云偏灰;
- 高光丢失;
- 调曝光也无法恢复正常。
TAAU
TAAU 是整个项目里很重要、也比较容易出问题的一部分。
它的全称可以理解为:
利用连续多帧画面,把低分辨率结果恢复成高分辨率结果。
1. 为什么不直接全分辨率画云?
假设屏幕分辨率是:
1920 × 1080
这意味着每一帧大约有两百万个像素。
如果每个像素需要走几百次 Ray Marching,计算量会非常大。
所以原版会先用较低分辨率计算云,例如宽和高都缩小到四分之一:
完整分辨率:1920 × 1080
云分辨率: 480 × 270
像素数量会大幅下降,性能也会明显提高。
2. 低分辨率的云为什么不会很模糊?
因为原版不会简单地把低分辨率图片直接放大。
它使用一个 4×4 的采样顺序,每一帧只计算其中一部分像素:
第 1 帧:计算第 1 组像素
第 2 帧:计算第 2 组像素
……
第 16 帧:计算第 16 组像素
经过 16 帧后,完整屏幕上的像素都被覆盖一次。
然后把当前帧和前面几帧的结果组合起来,逐渐得到稳定的高分辨率云。
这就是项目中提到的:
4×4 Bayer
16 帧覆盖
TAAU
3. TAAU 为什么会出现拖影和闪烁?
问题在于相机和场景会移动。
例如上一帧某个像素看到的是云:
上一帧:这个位置有云
但相机移动后,当前帧同一个屏幕位置可能已经变成山体:
当前帧:这个位置是山
如果程序继续使用上一帧的云,就会出现:
- 丝状闪烁;
- 黑色细线;
- 云拖在山体上;
- 云边缘抖动;
- 地形加载时画面撕裂。
因此,TAAU 不能只保存上一帧的颜色,还要保存:
- 云距离相机多远;
- 地形距离相机多远;
- 这份历史是否有效;
- 当前像素移动到了哪里。
当这些数据发生明显变化时,就要放弃旧历史,重新计算。
CSM 和 Beer Shadow Map
Shadow Map 可以理解为“从太阳的位置看场景,记录哪些地方被挡住”。
由于摄像机附近和远处需要的阴影精度不同,CSM 会把视野分成多个距离区间,每个区间使用一张阴影贴图。这样既能保证近处阴影清晰,又能覆盖较远的区域。
普通阴影通常只关心“被挡住”或“没有被挡住”,但云是半透明的,不能简单地得到一块纯黑阴影。Beer Shadow Map 还需要记录光在云中穿过了多远、被吸收了多少,最后得到有层次的云影透射率。
three-geospatial渲染管线
three-geospatial的完整渲染管线代码量很大,靠自己来阅读不太现实,还不一定看得懂,且阅读源码时发现并未封装相关API(不得不吐槽小日子的代码写的乱糟糟,注释也没几个)
借助AI可以高效的重新梳理一遍渲染管线
1. 云的形状从哪里来?
原版不是手工建模每一朵云,而是使用多张纹理共同生成云。
主要包括:
| 纹理 | 作用 | 容易理解的比喻 |
|---|---|---|
| Weather | 决定哪里有云 | 天气预报图 |
| Shape | 决定云的大轮廓 | 云的毛坯 |
| Shape Detail | 增加小尺度细节 | 雕刻云边缘 |
| Turbulence | 扭曲云的形状 | 风吹动云 |
| STBN | 帮助降低噪点 | 更均匀的随机采样表 |
最终的云密度大致通过下面的过程产生:
天气分布
→ 云的大形状
→ 小细节侵蚀
→ 覆盖率调整
→ 最终云密度
每个云层还有自己的:
- 云底高度;
- 云层厚度;
- 覆盖率;
- 密度;
- 风速;
- 使用的纹理通道。
原版最多支持四层云,默认启用低、中、高三层。
2. 云的亮面和暗面是怎么来的?
当程序沿着相机射线发现云之后,还需要向太阳方向再发射一条射线。
它会检查:
当前位置到太阳之间,还有多少云?
如果中间没有其他云,当前位置就比较亮。
如果中间有很多云,太阳光会被吸收,当前位置就比较暗。
但是云的暗面不能直接变成黑色,因为现实中的光会在云内部多次反射。
原版还计算了:
- 云内部多次散射;
- 地面的反射光;
- 云对太阳的遮挡;
- 地球本身对太阳的遮挡;
- 云边缘的银边效果。
这些计算共同决定了云是否有立体感。
原版完整渲染顺序
原版的核心思路可以简化为:
1. 画地形、建筑和模型
2. 记录场景深度
3. 计算云的太阳阴影
4. 低分辨率计算当前帧云
5. 使用 TAAU 恢复完整分辨率
6. 把云影应用到地面
7. 计算天空和地表的大气散射
8. 把云覆盖到场景上
9. 添加太阳圆盘和 Lens Flare
10. 进行 Tone Mapping
11. 输出最终画面
更接近实际工程的版本是:
HDR Scene Color
↓
CSM / Beer Shadow Map
↓
Cloud Current Pass
↓
TAA / TAAU
↓
Scene Cloud Shadow
↓
Aerial Perspective
↓
Cloud Overlay
↓
Sun Disc
↓
Lens Flare
↓
Tone Mapping / FXAA
我给 Push AI 的全部任务是怎样逐步变化的
我的提示词并不是一次写完的,而是根据每次运行结果不断调整。(个人感受:gpt更能懂人话,GLM需要详细解释步骤和方法)
| 阶段 | 我给 Push AI 的主要任务 | 得到的结果 |
|---|---|---|
| 1 | 对比当前项目和 three-geospatial,检查哪些功能没实现 | 得到缺失功能清单 |
| 2 | 制定完整复刻原版的实施计划 | 确定 Cesium 1.139、WebGL2、HDR、TAAU、BSM 等方案 |
| 3 | 按照计划开始实现 | 建立 Vite、TypeScript、Cesium 后端和云管线 |
| 4 | 继续完成完整计划 | 补齐云层、纹理、阴影和时间重建 |
| 5 | 新管线中云、地形和大气遮挡错误 | 重新把“深度契约”设为最高优先级 |
| 6 | 大气、云影和体积光没有出现 | 重排为“深度→大气→云影→体积光” |
| 7 | TAAU 开启后出现丝状闪烁 | 增加历史深度、有效性和拒绝规则 |
| 8 | 云和大气偏灰暗 | 对比原版光度单位和 HDR 合成顺序 |
| 9 | 调曝光仍无法解决发白问题 | 确认问题不只是曝光,而是重复颜色处理 |
| 10 | Cesium 报 Tone Mapper 值无效 | 改用 Cesium 支持的 Tone Mapper 枚举 |
| 11 | 大气缩放到全球时变成小圆 | 检查大气壳、地球半径和世界坐标 |
| 12 | 云影过黑 | 只衰减太阳直射,并增加 GUI 强度控制 |
| 13 | 缺少太阳、Lens Flare 和云动画 | 增加太阳圆盘、HDR Lens Flare 和风速动画 |
| 14 | 再次出现 Tone Mapper 错误 | 检查依赖版本和初始化顺序 |
一个很重要的事情
AI 开发并不是给出一个大提示词,然后等待它一次完成。真正有效的是根据运行结果不断缩小问题范围。
AI 发挥了什么作用?
1. 阅读和对比大量源码
它可以同时搜索:
- 原版 TypeScript;
- 原版 Shader;
- Cesium Shader;
- 当前移植代码;
- Cesium 私有渲染流程;
- 测试和资源引用。
这比人工逐个文件查找快很多。
2. 把大任务拆成小任务
例如“修复体积云”太宽泛。
更有效的提示词是:
只检查场景深度读取。
不要调整云颜色。
不要修改大气参数。
找出 Terrain 未加载时云消失的原因。
增加对应的数值测试。
任务范围越明确,AI 越不容易同时修改无关模块。
3. 建立可以重复执行的验证
这次移植没有只依赖“肉眼看起来差不多”。每次修改后都会执行类型检查、构建、单元测试和 GPU 中间纹理检查。
在不使用截图验证的情况下,也可以直接读取云颜色、场景深度、Beer Shadow Map、大气和 Lens Flare 等中间结果,检查是否出现全零、NaN、WebGL error 或 Shader 编译错误。
AI 最大的价值之一,是把一次性的人工排查整理成以后还能重复运行的测试。
一些个人感受
AI每天都在进步的时代,个人知识储备的速度已经完全无法相提并论了,以前可能重要的是会多少门开发语言,会多少技术栈,现在在AI面前完全是降维打击。
这很容易带来职业焦虑,但在真实项目中,AI 仍然需要人来确定目标、划定边界并判断结果。它可以很快生成 GLSL,也可以同时修改几十个文件,但它并不知道最终画面是否符合需求,更不会自动知道某次“修复”是不是用调暗参数掩盖了深度错误。
对我来说,这次项目最大的感受是:
具体语法可以随时让 AI 帮忙,但基础原理、问题拆分和结果判断反而比以前更重要。
我不一定需要记住所有 GLSL 函数,却需要知道:
- 这个问题可能出在深度、历史还是光照;
- Tone Mapping 应该在哪个阶段执行;
- TAAU 为什么会产生拖影;
- 云影应该影响整个场景,还是只影响太阳直射;
- AI 给出的结果是真正修复了问题,还是只是让问题暂时不明显。
AI 没有让图形学基础变得不重要。恰恰相反,它让“理解原理并提出正确问题”变得更有价值。
Push AI 大幅降低了阅读源码、生成测试和反复修改的成本,但整个项目能够推进,依靠的仍然是一个循环:
先理解问题
→ 给出明确边界
→ 让 AI 实现或分析
→ 用测试和画面验证
→ 根据结果继续缩小范围
对 WebGIS 开发者来说,我们不必一开始就掌握所有图形学公式。先知道每个阶段“在画面里负责什么”,再逐步理解深度、Ray Marching、TAAU、阴影和大气散射,就已经可以开始尝试很多过去看起来门槛很高的效果了。







.png)


