在大气散射渲染领域,理论原理与工程落地之间存在显著鸿沟。昨天我们探讨了 Rayleigh-Mie 大气散射的 WebGL 通用实现框架,今天我们将视角聚焦到三个具体工程问题:天空颜色随太阳角度的动态变化、日落时橙红渐变的精确模拟,以及行星大气层的壳式建模。这三个问题构成了实时天空渲染的核心挑战,也是游戏与可视化项目中经常遇到的痛点。
Rayleigh-Mie 散射的天空颜色建模
理解天空为何呈现蓝色,是构建可信大气渲染系统的基础。Rayleigh 散射定律指出,光线与大气分子的相互作用强度与波长的四次方成反比 —— 这意味着短波长(蓝光)的散射效率远高于长波长(红光)。在 Shader 中,这一特性通过 Rayleigh 散射系数向量体现:
const vec3 RAYLEIGH_BETA = vec3(5.5e-6, 13.0e-6, 22.4e-6);
当阳光进入大气层,蓝光在大气分子间反复散射,最终从各个方向进入观察者眼中,形成我们所见的 "天空蓝"。然而,仅有 Rayleigh 散射还不够 —— 它产生的蓝色过于饱和,缺乏真实大气中那种微妙的渐变与深度感。
Mie 散射解决了这个问题。它描述光线与较大气溶胶粒子(尘埃、水滴、污染物)的相互作用,特点是前向散射占主导,产生围绕光源的明亮光晕。在日落时分,当太阳靠近地平线,Mie 散射产生的光晕效果尤为显著,形成天空中那种朦胧的橙黄色边缘。实现 Mie 散射需要两个关键函数:
float miePhase(float mu) {
float gg = MIE_G * MIE_G;
float num = 3.0 * (1.0 - gg) * (1.0 + mu * mu);
float den = 8.0 * PI * (2.0 + gg) * pow(max(1.0 + gg - 2.0 * MIE_G * mu, 1e-4), 1.5);
return num / den;
}
float mieDensity(float h) {
return exp(-max(h, 0.0) / MIE_SCALE_HEIGHT);
}
其中 MIE_G 控制前向散射的偏心程度,通常取 0.76 到 0.8 之间的值可以实现较为自然的太阳光晕效果。
臭氧吸收是另一个被初学者忽视的因素。臭氧层位于平流层上方,它不散射光线,但会选择性吸收红光与部分紫光。这种吸收效应使得天空在日落时呈现特有的紫色调,在高纬度地区尤为明显。工程实现中,臭氧密度函数通常定义为:
float ozoneDensity(float h) {
float ozoneCenterHeight = 25.0;
float ozoneWidth = 15.0;
return max(0.0, 1.0 - abs(h - ozoneCenterHeight) / ozoneWidth) * 0.6;
}
日落橙红渐变:light marching 与 transmittance 计算
实现可信的日落效果,远非简单改变天空颜色那么简单。真正的挑战在于:当日光穿过大气层到达观察者时,路径上的分子密度差异会导致不同波长光的衰减程度各不相同。
在 raymarching 框架中,每个采样点需要 "询问":从太阳到此处,有多少光线能够穿透大气?答案通过嵌套的 light marching 循环获得:
vec3 lightMarch(float start, float sunY) {
float denom = max(sunY + 0.15, 0.04);
float maxDist = (ATMOSPHERE_HEIGHT - start) / denom;
float stepSize = max(maxDist, 0.0) / float(LIGHTMARCH_STEPS);
float odR = 0.0, odM = 0.0, odO = 0.0;
for (int i = 0; i < int(LIGHTMARCH_STEPS); i++) {
float t = (float(i) + 0.5) * stepSize;
float h = start + t * sunY;
if (h < 0.0 || h > ATMOSPHERE_HEIGHT) continue;
odR += rayleighDensity(h) * stepSize;
if (uMieEnabled) odM += mieDensity(h) * stepSize;
if (uOzoneEnabled) odO += ozoneDensity(h) * stepSize;
}
return vec3(odR, odM, odO);
}
结合 view direction 上的光深,总 transmittance 通过 Beer's Law 计算:
vec3 sunOD = lightMarch(h, sunDirection.y);
vec3 tau = BETA_R * (viewODR + sunOD.x)
+ BETA_M_EXT * (viewODM + sunOD.y)
+ BETA_OZONE_ABS * (viewODO + sunOD.z);
vec3 transmittance = exp(-tau);
当日落时,光线穿越大气层的路径急剧增长,蓝光在长路径上几乎完全散射殆尽,而红光因散射效率低得以保留更多,这正是天边呈现橙红色的物理原因。当太阳完全没入地平线后,只剩下散射到高空中的少量蓝光,形成 "蓝色时刻"(blue hour)特有的深蓝紫色天空。
工程实践中,建议设置 sunAngle uniform 范围为 [0, π],其中 0 表示天顶,π/2 表示地平线。通过调整这个参数,可以观察到天空颜色从正午的浅蓝、到下午的金黄、再到日落的橙红、直至夜幕降临后的深蓝紫的连续变化。
行星大气层建模:ray-sphere intersection 与深度重建
将天空渲染从平面背景升级为行星大气层,需要解决两个核心问题:大气壳的几何定义,以及与场景几何的深度交互。
首先,使用 ray-sphere intersection 计算观察射线与大气球壳的交点:
vec2 raySphereIntersect(vec3 rayOrigin, vec3 rayDir, vec3 center, float radius) {
vec3 oc = rayOrigin - center;
float b = dot(oc, rayDir);
float c = dot(oc, oc) - radius * radius;
float discriminant = b * b - c;
if (discriminant < 0.0) return vec2(-1.0);
float sqrtDisc = sqrt(discriminant);
float t0 = -b - sqrtDisc;
float t1 = -b + sqrtDisc;
return vec2(min(t0, t1), max(t0, t1));
}
对于行星场景,还需要检测射线是否先击中星体表面 —— 如果存在这一交点,应将大气层的光线终止点设置为该表面而非大气外缘。这保证了大气渲染不会出现在行星 "内部" 的错误位置。
深度重建是连接 post-processing 效果与 3D 场景的桥梁。通过从深度缓冲区读取数据并结合投影矩阵逆,可以重建每个像素对应的世界空间位置:
vec3 getWorldPosition(vec2 uv, float depth) {
float clipZ = depth * 2.0 - 1.0;
vec2 ndc = uv * 2.0 - 1.0;
vec4 clip = vec4(ndc, clipZ, 1.0);
vec4 view = projectionMatrixInverse * clip;
vec4 world = viewMatrixInverse * view;
return world.xyz / world.w;
}
在行星尺度上,必须使用对数深度缓冲(logarithmicDepthBuffer)来避免深度冲突。由于大气层厚度相对于行星半径极小(地球大气层约 100km,而地球半径约 6371km),普通浮点深度在高空中几乎无法区分大气与星体表面。
LUT 优化路径与关键参数配置
直接 raymarching 的方法虽然直观,但计算成本极高。以 24 步主循环加 16 步 light marching 计算,每像素需要约 400 次采样。在 1080p 分辨率下,这已经接近数十亿次的计算量,难以满足实时渲染需求。
Sebastian Hillaire 在 2020 年提出的 LUT 优化方案将昂贵的散射计算预计算为纹理:三张 LUT 分别存储 transmittance(250×64)、sky view(天空颜色)、以及 aerial perspective(场景雾效)。
Transmittance LUT 的生成逻辑如下:沿 x 轴变化光线角度(mu ∈ [-1, 1]),沿 y 轴变化高度(planetRadius → atmosphereRadius),对每个像素执行 raymarching 并存储结果。这种纹理使得后续的天空渲染只需查表而非重新计算。
工程实现中需要注意几个关键参数:
| 参数 | 典型值 | 说明 |
|---|---|---|
| RAYLEIGH_SCALE_HEIGHT | 8.0 km | 大气标高,决定密度衰减速率 |
| MIE_SCALE_HEIGHT | 1.2 km | Mie 散射粒子分布高度 |
| ATMOSPHERE_HEIGHT | 100 km | 大气层外缘高度 |
| PRIMARY_STEPS | 24 | 主 raymarching 步数 |
| LIGHTMARCH_STEPS | 16 | 光线步数 |
对于火星大气模拟,需要调整参数为:rayleighScaleHeight = 11.1、rayleighBeta = vec3 (0.019, 0.013, 0.0057),同时禁用臭氧吸收(ozoneBetaAbs = vec3 (0.0))。这种调整会产生特有的橙红色大气与蓝色日落 —— 与地球截然不同的视觉特征。
日食与遮挡的扩展处理
在实际场景中,行星大气渲染还需要处理天体遮挡带来的光照变化。例如当地球遮挡太阳时,大气层中的散射光量急剧减少,观察者看到的是被 "剪影" 化的暗红日出效果。实现这一效果的 sunVisibility 函数需要考虑三种情况:天体完全在太阳前方、部分遮挡、以及完全在太阳后方。核心算法通过角距(angular separation)与角半径(angular radius)的比较来确定遮挡程度。
总结
本文从工程实现角度探讨了大气散射渲染在天空颜色变化、日落渐变、以及行星大气建模三个维度的问题。核心方法论包含:使用 Rayleigh/Mie/Ozone 三分量散射模型建模大气光学特性;通过嵌套 light marching 计算 transmittance 以实现日落橙红效果;利用 ray-sphere intersection 定义行星大气壳的几何边界;以及通过 LUT 预计算方案优化实时渲染性能。
资料来源:Maxime Heckel - On Rendering the Sky, Sunsets, and Planets
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。