Cesium 的地球渲染在 Web 端已经演进了很多年,从 WebGL 1.0 到 WebGL 2.0,基本都在老渲染管线的框架内做优化。真正让“数字地球”重新有讨论空间的,是 WebGPU 带来的底层变化:计算着色器、存储缓冲、显式资源绑定、更高效的遮挡剔除。这次我们来看 WebGPU 版 Cesium 方向的 Atmosphere 模块,重点拆“基础大气和光照”这一部分。
Atmosphere 这个名字,直译是“大气层”,放到数字地球场景里,它要做的事情非常明确:让地球边缘出现真实的大气散射光晕,让太阳光照在球面和地形上看起来像真实日光,而不是一张硬贴图。相比传统 Cesium 里自带的 Atmosphere 效果,WebGPU 版本可以更直接地把瑞利散射、米氏散射、预计算 LUT、天空照度和地表光照合成到一起,性能控制也更有把握。
这篇文章会围绕三件事展开:第一,Atmosphere 模块要解决什么问题,和 WebGPU 数字地球整体架构的关系是什么;第二,当你自己搭建 WebGPU 版 Cesium 或类似数字地球原型时,基础大气和光照应该怎么分模块设计和验证;第三,部署运行、功能测试、性能观察、常见排错怎么做。这属于偏图形渲染工程的内容,需要一点 WGSL 和渲染管线基础,但不是必须读完论文才能上手。
1. Atmosphere 核心能力速览
先把“基础大气和光照”模块的能力边界列出来。下面的表格不是某个具体发行版的功能清单,而是从 WebGPU 数字地球工程实践角度整理的模块参考能力,具体实现以你选择的源码仓库或实验分支为准。
| 能力项 | 说明 |
|---|---|
| 模块类型 | 数字地球场景中的大气散射与光照渲染模块 |
| 渲染 API | WebGPU,WGSL Shader,依赖navigator.gpu与 Canvas 的 WebGPU 上下文 |
| 基础效果 | 地球边缘大气散射、天空背景、太阳方向光照、地表光照统一 |
| 散射模型 | 瑞利散射 Rayleigh + 米氏散射 Mie,可组合出高空蓝紫色散射和低空灰白色光晕 |
| 核心优化方向 | 屏幕空间 Ray Marching,或预计算散射 LUT 后做采样合成,减少逐像素重复计算 |
| 联动关系 | 可与 Cesium 的相机系统、地形高程、影像瓦片、太阳位置联动 |
| 硬件前提 | 需要支持 WebGPU 的浏览器和 GPU 后端,通常要求显卡驱动支持 D3D12 / Vulkan / Metal |
| 显存占用 | 无输入材料提供具体数值,需按实际场景分辨率、纹理 LUT 大小与后端 GPU 实测观察 |
| 是否支持 CPU | WebGPU 本身面向 GPU 计算,通用 CPU 回退通常不现实,需要按目标浏览器能力确认 |
| 启动方式 | 需要构建工具启动本地开发服务,无法像普通静态页面直接双击打开 |
| 是否提供 API | 模块内部表现为 Shader Module、Pipeline、Uniform Binding,工程侧再看是否封装为 TS 类 |
| 是否支持批量任务 | 不是图像生成类工具,不存在批量任务;大量场景对象更新需自行管理 Render Pass |
| 适用场景 | WebGIS 可视化平台、数字孪生大屏、三维地球展示、Cesium 二次开发与渲染实验 |
一句话总结这个模块的价值:Cesium 负责把地球的几何、瓦片和相机管好,Atmosphere 负责把“这层光”渲染对,让场景看起来像真实拍摄的大气环境,而不是 CAD 风格的线框球。
2. 适用场景与使用边界
WebGPU 版 Cesium 的 Atmosphere 不是给普通业务后台用的组件,它的适用场景集中在需要高质量数字地球表现的前端项目。
第一类是 WebGIS 展示平台。比较典型的是城市级数字孪生、智慧园区、自然资源三维场景。这类项目已经有 Cesium 地形、影像、倾斜摄影或模型数据,但默认光照表现比较平,加了基础大气和光照模块后,太阳方向、昼夜变化、视角远近的光感会更统一。
第二类是三维地球可视化大屏。多用于态势展示、仿真信息呈现、宏观数据叠加。这里的大气效果更多是氛围感,不需要严格物理精确,但需要平滑帧率,GPU 占用不能失控。
第三类是 Cesium 底层渲染研究。有人想验证 WebGPU 在 Cesium 中的可行性和收益,Atmosphere 是一个很好的切入点,因为大气效果效果直观、数据链路短,适合快速验证。
使用边界也要说清楚。
当前 WebGPU 还不是所有浏览器默认功能完全一致,Windows 上通常走 D3D12 后端,macOS 上走 Metal,Linux 取决于 Vulkan 驱动。如果你还在维护大量老旧机器或必须兼容 IE 级别浏览器的场景,WebGPU 版 Cesium 现在不是可选方案。
另外,Atmosphere 模块并不解决所有“好看”的问题。它只负责大气和光照,地球上的水体反射、动态云层、阴影、雾效是独立模块。不要期待接入大气模块后,整个场景自动变成电影级效果。
3. 环境准备与前置条件
在开始部署前,先确认环境。因为材料里没有给出指定版本,下面给出一套通用检查项,实际项目需要按你的源码包版本微调。
3.1 浏览器与 GPU 后端
WebGPU 目前需要浏览器支持。建议使用较新的 Chrome、Edge 版本,并在chrome://gpu或edge://gpu中确认 WebGPU 状态。macOS Safari 也有一定支持,但版本差异较大,开发时最好先把 Chrome 作为主测试浏览器。
从底层看,GPU 后端是否可用取决于系统图形 API:
- Windows:需要 D3D12 可用,显卡驱动基本要相对较新。
- macOS:通过 Metal 后端,Apple Silicon 和较新的 AMD 显卡兼容性更好。
- Linux:依赖 Vulkan,驱动配置不正确时容易遇到问题。
对于老显卡,可以通过 Chrome 的 SwiftShader 软件 WebGPU 回退来验证逻辑,但性能不具参考价值,只能用来排查渲染流程是否正确。
3.2 开发工具链
如果用 TypeScript 做工程化开发,推荐按下面的基础组合准备:
| 依赖 | 用途 |
|---|---|
| Node.js 18+ | 运行 npm / pnpm 脚本 |
| Vite | 本地开发服务器与构建 |
| TypeScript | 参数类型、资源绑定描述更可控 |
| WGSL | 在.wgsl文件或内联字符串中编写着色器 |
如果你的工程需要和 Cesium 一起使用,建议先保留一套官方 Cesium 基础场景做对照,这样能快速判断大气模块是否影响地球瓦片加载。
3.3 文件目录规划
Atmosphere 模块最好独立成目录,避免和业务代码混在一起。推荐如下结构:
src/ renderer/ device.ts # WebGPU adapter / device / context 封装 atmosphere/ shaders/ sky.wgsl scattering.wgsl atmosphere.ts # Atmosphere 模块入口 uniform.ts # 相机和光照参数 scene/ globe.ts # 地球几何与纹理采样 main.ts这样做的原因是:大气和光照参数变化频繁,需要和场景对象生命周期解耦;Shader 目录独立,也方便后续替换散射模型或切换预计算 LUT 实现。
4. 初始化 WebGPU 与大气的渲染框架
这一节不依赖某个具体开源实现,给出的是自己组合 WebGPU 场景时最常用的三层结构:
- 请求 Adapter 并创建 Device。
- 获取 Canvas 的 WebGPU context,配置
format。 - 创建 Uniform Buffer 和 Render Pipeline,绘制场景或全屏后处理。
4.1 创建 WebGPU 设备
基础初始化代码通常长这样:
async function createWebGPUDevice(canvas: HTMLCanvasElement) { const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }); if (!adapter) { throw new Error('WebGPU 不可用,请检查浏览器和 GPU 后端状态'); } const device = await adapter.requestDevice(); const context = canvas.getContext('webgpu'); if (!context) { throw new Error('Canvas 无法创建 WebGPU 上下文'); } const format = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device, format, alphaMode: 'opaque' }); return { device, context, format }; }从材料看,WebGPU 版 Cesium 属于实验性探索方向,所以如果你看到的工程项目还附带自己的 loader 或 fork,请以它的导出方式为准。上面这段代码是 WebGPU 标准入门的通用写法,不绑定某一家实现。
4.2 与 Cesium 场景的关系
把 WebGPU 大气渲染接到 Cesium 场景中,有两条常见路线:
路线一:Cesium 用 WebGL 继续渲染地形和影像,WebGPU 大气层以独立图层或后处理叠加方式输出。优点是接入成本低,缺点是两套渲染器之间需要共享相机参数,深度衔接麻烦。
路线二:整体渲染走 WebGPU,Cesium 只提供数据源、瓦片解析和相机管理,WebGPU 负责最终绘制。优点是渲染控制力更强,缺点是需要替换大量 Cesium 的默认渲染流程,工程量明显上升。
能明显看到“WebGPU 版 Cesium”的方向必然是第二条路线,只是在演进过程中,大气和光照模块会先独立跑通,再逐步接入完整地球。所以在你做功能测试的时候,也应该先给一个独立 WebGPU 球体场景,把大气调通,再接真实 Cesium 瓦片,避免首次接入就去排查数据加载问题。
5. 基础大气散射与光照实现思路
基础大气和光照是整个 Atmosphere 系列笔记里最核心的部分。下面回到图形原理层面,讲清楚实现要素。
5.1 相机射线与场景相交
大气效果通常不是像普通物体一样用 Mesh 渲染,而是在 Pixel 阶段对每一条视线射线做球体相交,再沿射线累积大气散射颜色。
核心思路可以简化成:
- 给定射线原点和方向。
- 求射线与大气外边界球相交,进入大气。
- 沿射线步进采样。
- 在每个采样点计算光照方向和观察方向的夹角。
- 根据高度和夹角,用散射系数加权累积颜色。
- 与地表颜色混合。
用伪代码表示:
fn computeAtmosphereColor( rayDir: vec3<f32>, sunDir: vec3<f32>, cameraPos: vec3<f32> ) -> vec3<f32> { var color = vec3<f32>(0.0, 0.0, 0.0); let steps = 32u; let stepLength = (atmosphereTopRadius - earthRadius) / f32(steps); for (var i = 0u; i < steps; i = i + 1u) { // 射线位置 let t = stepLength * f32(i); let worldPos = cameraPos + rayDir * t; // 当前高度 let h = length(worldPos) - earthRadius; if (h < 0.0) { break; } // 简化指数密度 let density = exp(-h / scaleHeight); // 当前点是球面上方,判断太阳是否被地球遮挡 // 这里省略精确遮挡测试,仅示意 color += calcScattering(density, worldPos, sunDir) * stepLength; } return color; }实际工程里,这样纯逐像素步进的开销较大,通常还需要降低步进层数或在低分辨率渲染后再上采样。另一条主流路线是预计算散射 LUT,把散射积分结果存入纹理,后续逐像素直接采样,极大降低每帧重复计算量。
5.2 瑞利散射与米氏散射
Atmosphere 基础大气效果最常用的两个物理模型:
- 瑞利散射:波长越短散射越强,阳光穿过大气时蓝光散射最强。卫星视角下,地球边缘的蓝色光晕主要来自瑞利散射。
- 米氏散射:由大气中的气溶胶和水汽造成,散射方向性较强,看太阳方向时会出现白色或淡黄色光晕。
对这两个模型分别设置 scaleHeight、散射系数、相位函数系数,再把两种散射结果相加,就可以得到基础大气带。
WGSL 中实现相位函数可以参考这种形式:
fn rayleighPhase(cosTheta: f32) -> f32 { let factor = 3.0 / (16.0 * PI); return factor * (1.0 + cosTheta * cosTheta); } fn miePhase(cosTheta: f32) -> f32 { let g = 0.76; let g2 = g * g; let denom = 1.0 + g2 - 2.0 * g * cosTheta; return 3.0 / (8.0 * PI) * ((1.0 - g2) * (1.0 + cosTheta * cosTheta)) / pow(denom, 1.5); }注意:真实项目中的系数、相位函数形式很多,需要按你选择的物理模型和审美目标调整。
5.3 太阳光方向参数
Atmosphere 的光照方向通常来自太阳在场景中的位置。Cesium 中可以通过当前时间计算太阳方向,也可以手动拖一个光源角度,便于调试。在 WebGPU 中,把太阳方向统一放进 Uniform Buffer:
const uniformBuffer = device.createBuffer({ size: 64, // 至少能放下 cameraPos、sunDir 等参数,实际对齐按 16 字节计算 usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, });WGSL 侧声明类似:
struct AtmosphereParams { cameraPos: vec3<f32>, sunDir: vec3<f32>, sunIntensity: f32, };太阳高度角改变时,大气散射颜色也应该随之变化。日落时阳光穿过大气路径长,蓝色被散射掉更多,只剩红色和橙色,如果模块计算出的日落场景没有这种变化,大概率是采样步长、散射系数或遮挡判断的问题。
5.4 与地表光照合并
基础光照不只是“天空余光”,地表也应该受到太阳光照影响。常见做法是先计算大气透射率或光照衰减,再作为地表材质的一个光照输入。
做法步骤:
- 先用 WebGPU Render Pass 渲染地表和模型。
- 地平面上方场景存入颜色缓冲,同时存下深度。
- 第二个 Pass 根据深度重建世界坐标或视线长度,配合大气 LUT 计算雾化颜色。
- 最终混合得到“近处清晰、远处被大气覆盖”的效果。
如果只实现“地球边缘大气”,不实现地表光照合并,从远处看效果还可以,但视角拉近后,地表像纸片一样,这是因为缺失了大气造成的空气透视。
6. 功能测试与效果验证
6.1 最基础验证:浅蓝到深蓝的边缘渐变
启动项目后,把相机放到太空视角,让地球占据画面中心区域。
预期结果:
- 地球边缘出现一圈蓝色光晕。
- 光晕在朝向太阳的一侧更亮。
- 远离太阳一侧的光晕偏暗但仍有微量散射。
判断标准:边缘渐变是否平滑,有没有锯齿噪点。如果是刚接入 WebGPU 的场景,第一次调试常见问题是渲染结果偏黑或偏白。
偏黑时,先检查太阳方向是否归一化,以及光线步进的高度范围是否把射线限死在大气层外。
偏白时,检查累积步长是否过大或散射系数是否过高,这类问题最直接的观察窗口是“降低步长后画面是否明显变暗”。
6.2 太阳高度变化测试
需要做一个简单的相机和光源控制。
let sunElevation = 30 * Math.PI / 180; let sunDirX = Math.cos(sunElevation); let sunDirY = Math.sin(sunElevation); // 刷新时传给 uniform uniformData.sunDir = [sunDirX, sunDirY, 0];用例设计:
| 太阳角度 | 预期效果 | 主要观察点 |
|---|---|---|
| 90 度 | 场景明亮,大气边缘淡蓝 | 大气是否过曝 |
| 30 度 | 东侧亮蓝,西侧偏暗,大气光晕带角度 | 散射方向是否一致 |
| 0 度附近 | 冷暖对比明显,地平线偏红 | 米氏散射是否明显发灰 |
| 负角度 | 地球暗面,但卫星边缘仍有一圈微光 | 大气遮挡是否正确 |
这个测试可以用来判断散射模型是否真正生效,而不是简单的“蓝色描边”。
6.3 地表光照联动测试
切换到近地表视角,把相机高度降到几十公里以内。
预期结果:
- 正下方地表应受太阳方向光照,阴影侧自然变暗。
- 远处山体或地形有一种“被空气笼罩”的灰蓝色效果。
- 相机从高空拉低过程中,大气颜色应逐渐变淡,不会出现突然切换。
判断标准:前后帧连续性是否平滑。如果拉近时大气浓度突然变为 0,多半是大气外边界高度和地球半径参数不匹配,导致射线在进入大气前就结束步进。
6.4 WebGPU 渲染环境验证
验证分为三步:
- 打开浏览器的 WebGPU 状态页,确认 API 可用。
- 在页面中绘制一个最基本的三角形,确认渲染管线正常。
- 接入 Atmosphere 模块,先不加载 Cesium 瓦片,用纯色球体代替地球。
这样做可以减少干扰项,先保证大气和光照逻辑正确,再叠加影像瓦片。
7. 接口 API 与可配置参数
虽然材料中未给出该模块的现成 API,但从工程封装角度来看,一个可维护的 Atmosphere 模块至少需要暴露以下几类参数。
7.1 构造参数
export interface AtmosphereModuleOptions { enableScattering: boolean; enableMie: boolean; sunIntensity: number; rayleighScaleHeight: number; mieScaleHeight: number; steps: number; resolutionScale: number; }这类配置的意义是方便在调参和正式渲染之间切换,避免为了试一个参数去改 Shader 重新编译。
7.2 Uniform 数据结构
建议所有跟相机、光源相关的动态参数都放进 Uniform Buffer,每帧更新一次,不要直接写在 Shader 常量里。
const params = { cameraPos: new Float32Array(3), sunDir: new Float32Array(3), sunIntensity: 1.0, rayleighScaleHeight: 8500.0, steps: 32.0 };这种做法的好处是方便后续加入时间动画、太阳轨迹、或者从 Cesium 外部同步相机位置。
7.3 回调接口
模块可以提供 onFrame、onResize、onCameraChanged 一类回调,供外部业务统一驱动渲染循环。这里不再给出绑定特定框架的代码,因为不同工程的事件循环不一致,但接口设计要保证“大气渲染只依赖输入参数,不主动查询全局状态”。
8. 资源占用与性能观察
在 WebGPU 版 Cesium 场景里,性能是比功能更敏感的问题。因为数字地球项目往往已经加载大量地形瓦片、矢量数据和模型,Atmosphere 如果再占用过高,整体体验就会崩。
8.1 显存占用观察方法
没有具体材料支撑,这里不给具体显存数字,但观察方法是一套可靠流程:
- 打开浏览器 DevTools 的 Performance 面板。
- 在 Rendering 选项里勾选并启用 WebGPU 相关统计能力。
- 切换不同分辨率、不同 LUT 大小、不同 Ray March 步数,记录变化。
- 对比“关闭大气模块”和“开启大气模块”时的帧时间差异。
正常情况下,大气渲染不应该是场景中最大的显存消费者,更大开销通常来自影像纹理、地形 mesh 和阴影贴图。如果大气模块导致显存飙升,优先检查是否把逐帧渲染结果都存成了永久纹理,或者 LUT 尺寸过于浪费。
8.2 计算量与分辨率
Atmosphere 的渲染成本和屏幕分辨率强相关:
- Ray March 步数从 32 翻到 64,Pixel Shader 循环体翻倍。
- LUT 分辨率从 128 提高到 256,增加的往往是预计算时间,运行时采样额外开销不大。
- 全屏后处理一旦叠加,分辨率越高,采样成本越高。
实际调优可以按这个顺序来:
- 先固定 Ray March 步数为 32,看效果是否可接受。
- 如果不满意,提高散射采样质量而不是盲目增加步数。
- 如果 Canvas 显示尺寸已经超过 4K,考虑降低内部渲染分辨率,再用双线性或双三次上采样。
8.3 低端 GPU 下的降级策略
低端 GPU 上优先建议做分级策略:
- 当帧时间超过阈值时,自动减少 Ray March 步数。
- 关闭米氏散射,只保留瑞利散射。
- 使用粗糙模式,不再做预计算 LUT 多级采样。
这一层不是必须写进 WebGPU 版 Cesium 核心代码,但在工程实际中很有价值。很多数字孪生大屏运行在集成显卡或云渲染环境里,不能假定用户都有一张高性能独立显卡。
9. 常见问题与排查方法
WebGPU 版本还在快速演进,碰到的问题会比较杂。下面把最容易遇到的几类问题列出来。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
navigator.gpu不存在 | 浏览器不支持 WebGPU,或未开启实验特性 | 打开chrome://gpu或浏览器文档确认 | 升级浏览器,或打开相应实验开关 |
| 页面黑屏,控制台少报错 | 渲染管线创建失败,或 Canvas 未正确 configure | 先用三角形测试管线,再接入大气模块 | 检查 shader 编译报错、Pipeline 描述是否符合 WebGPU 规范 |
| 大气边缘出现明显噪点 | Ray March 步数不足,或采样分布不合理 | 单帧截图放大观察边缘 | 提高步数或增加 LUT |
| 大气颜色过于饱和 | 散射系数或步长过大 | 调低散射系数,观察颜色变化趋势 | 以视觉效果为准逐步调参 |
| 太阳方向效果不明显 | Uniform 未正确更新或太阳方向被归一化后的值错误 | 在代码中打印 uniform 参数 | 先固定一个太阳方向,再联动时间 |
| WebGPU Device Lost | 驱动崩溃,或长时间后台切换引起 GPU 上下文重置 | 监听device.lost事件并查看 reason | 实现自动重建 Device 和 Pipleline |
| 与 Cesium 瓦片深度穿插 | WebGPU 大气层与 Cesium 相机深度权重不一致 | 单独渲染大气层,关闭瓦片,排查相机矩阵 | 统一两套系统的投影矩阵和相机位置基准 |
| 高温或高负载时画面闪烁 | 显存带宽不足,或后端驱动驱动有 bug | 限制帧率,降低内部渲染分辨率 | 引入动态分辨率缩放,或提供画面质量开关 |
另有两个容易忽略的问题。
第一,WebGPU 对资源布局的要求比 WebGL 更严格。Uniform Buffer 的 stride 对齐、纹理格式要匹配。如果数据错位,太阳方向显示不对,排查难度还会高一些。
第二,如果用了多个 Render Pass,必须正确管理 Render Attachment 的 loadOp 和 storeOp。大气后处理想保留 Cesium 之前渲染的地球影像,就需要在 Load 时设置 clear 为 false,以避免覆盖前一帧结果。
10. 最佳实践与使用建议
Atmosphere 模块接入数字地球项目时,建议从一开始就按工程化方式管理参数。
第一次接入时,不急着加载真实地形,先准备一个简单球体场景。在这个场景里调好大气散射颜色、太阳方向和地表光照。这样能快速定位问题是出在算法层还是 Cesium 数据对接层。
尽量保留一套低画质配置。大屏项目往往需要 7x24 小时运行,不可能每次都开全分辨率高质量渲染。发布前至少要提供“低性能模式”与“高质量模式”两套开关。
显存、内存、Canvas 分辨率要分开管理。页面分辨率不等于物理渲染分辨率,可以设置一个 renderScale 变量,在大屏上运行时用低分辨率渲染,再缩放显示。
Shader 代码尽量独立成文件。WGSL 代码放内联字符串,调试和复用都不方便。建议直接用.wgsl文件加载或通过 Vite 插件处理。
在真实 Cesium 场景中接入时,考虑权限和边界。渲染大气和光照不涉及隐私问题,但如果你的数字地球项目叠加了卫星影像、实时监控、人员位置等数据,必须确认数据来源合法、展示范围合规,不能因为“只是做可视化”就忽略授权边界。如果后续加入真实时间系统来模拟太阳位置,还要考虑不同时区的地址是否被正确处理。
对于涉及单位内部系统和地图数据的项目,建议先在内网或测试环境验证。不要把未脱敏的管理数据、地形数据放到公网演示,避免踩到数据合规红线。
11. 总结与下一步
这篇文章里没有堆叠某一份具体仓库的接口文档,而是把 WebGPU 版 Cesium 的 Atmosphere“基础大气和光照”拆成一个可复用的工程技术路径。从模块定位、环境准备、设备初始化、Shader 原理、参数设置到常见问题排查都过了一遍。
如果你接下来要自己实现或者研究这一类模块,建议第一步先别管 Cesium 的多种数据加载,先跑通一个可旋转球体,在球体外边界套上大气散射。把太阳方向做成可以拖动的调试参数,再逐步优化为预计算 LUT。
最容易踩的坑有两个:一是把大气渲染当成简单“蓝色描边”,没有从散射物理和视线上区分地面和大气层,导致远近视角效果割裂;二是过早接入 Cesium 瓦片,遇到黑屏后分不清是大气模块问题还是瓦片加载问题。
下一步可以考虑和地形阴影结合。Atmosphere 只是基础光照的一维,更复杂的是地形遮挡导致的局部阴影、云层对光照的衰减、以及水面太阳反射。把那层效果加进来后,数字地球的整体真实感会有一次明显提升。