UE5.8 体积雾与大气:从山洞晨雾到云端日落

📑 目录

UE5.8 体积雾与大气:从山洞晨雾到云端日落

老周负责的关卡做到第三周,美术总监盯着屏幕看了半天,只回了一句话:这山洞缺口气。

画面里模型精度够高,贴图也是 PBR 流程,灯光色温也调过。可整个场景就是平的,像一张干净的布景。老周把 Directional Light 调暗又调亮,补了几盏 Point Light,结果更糟,洞壁被切成了几块明暗分明的硬面。

缺的那口气,就是大气。雾气、尘埃、湿度、高空散射,这些东西本来不存在于模型里,却决定了场景有没有呼吸感。UE5.8 里处理这类效果的系统有好几个,名字长得像,功能边界也容易混淆。这篇文章把老周踩过的坑拆开讲一遍。

1. Volumetric Fog 基础

Volumetric Fog 是 UE5 处理三维空间雾气的核心方案。它把整个视锥体切成一个体素网格(Voxel Grid),在每个体素里计算散射、吸收和光照贡献,最后把结果合成到屏幕上。

老周第一次打开 Volumetric Fog 时,场景立刻有了体积感。光束从洞顶裂缝漏下来,空气中悬浮的微粒被照亮,边缘柔和。这种效果靠传统贴图平面雾做不出来,因为平面雾只改变像素颜色,不参与光照。

体雾的渲染流程可以画成下面这张架构图。

flowchart TD

在每个体素内部,引擎执行的是辐射传输方程的简化版。光线穿过介质时,强度按比尔-朗伯定律衰减:

T(d)=eσt0dρ(s)dsT(d) = e^{-\sigma_t \int_0^d \rho(s) \, ds}

其中 σt\sigma_t 是消光系数,ρ(s)\rho(s) 是沿路径的密度分布。T(d)T(d) 表示从距离 dd 处返回的眼睛方向上,光线还剩多少没被介质吸收或散射掉。

散射到眼睛的光由下面这个积分描述:

L(x,ω)=0deσt0sρ(t)dtσsρ(s)Lin(xs,ω)dsL(x, \omega) = \int_0^d e^{-\sigma_t \int_0^s \rho(t) \, dt} \cdot \sigma_s \rho(s) \cdot L_{in}(x_s, \omega) \, ds

这里 σs\sigma_s 是散射系数,LinL_{in} 是从光源方向进入该点的入射光。实际引擎不会逐像素做这个积分,而是先在体素里采样,再用临时体积纹理缓存,最后重投影和滤波。

体雾有几个关键参数老周建议直接记住:

  • Scattering Distribution:控制前向散射还是后向散射。值接近 1 时,雾会沿着光源方向拉长,适合做光束。
  • Albedo:决定雾的颜色偏向。设成 0.9 以上偏白,0.5 以下会偏灰甚至带色。
  • Extinction Scale:整体消光强度。数值太大,五米外就什么都看不见。
  • View Distance:体雾网格的有效距离。拉远能看到更多大气层次,但会稀释近处精度。

老周的山洞场景里,他把 View Distance 从默认 5000 降到 1200,Scattering Distribution 提到 0.85。裂缝漏下来的那束光立刻实体化了。

这里有个细节新手容易忽略:体雾网格在屏幕空间是均匀分布的,但在世界空间是锥形分布。近处体素小,远处体素大。如果镜头紧贴一根柱子,柱面边缘的体雾会出现锯齿。解决办法是微调 r.VolumetricFog.GridPixelSize,或者在前景物体上禁用雾效接收。UE5.8 在材质里提供了 Volumetric Fog 开关,可以在需要时把物体从体雾中剔除。

2. Exponential Height Fog

Exponential Height Fog 是更老牌的方案。它假设雾密度随高度指数下降,计算极快,也极容易控制。很多户外场景仍然靠它打底。

密度公式很直接:

ρ(h)=ρ0eh/H\rho(h) = \rho_0 \cdot e^{-h / H}

ρ0\rho_0 是地面基准密度,HH 是高度衰减尺度。HH 越小,雾越贴近地面;HH 越大,雾气向上铺得越开。

老周在山谷场景里同时开了 Volumetric Fog 和 Exponential Height Fog。前者负责光束和局部体积感,后者负责远景的柔和过渡。两者叠加时要注意:Exponential Height Fog 会作为基础密度参与体雾计算,如果把两边密度都拉满,画面会糊成一片。

在 Exponential Height Fog 细节面板里,有几个开关老周建议你检查一遍:

  • Volumetric Fog:是否把指数雾的贡献注入体素网格。关闭时它只渲染为屏幕空间雾。
  • Fog Inscattering Color:雾被光照时的颜色。
  • Directional Inscattering:模拟太阳光穿透雾气的方向性辉光。

Exponential Height Fog 不吃灯光数量,性能稳。它的缺点是空间变化弱,不能像体雾那样被本地灯光和材质影响。所以老周一般把它当成远景氛围,细节交给 Volumetric Fog。

老周在一个河谷场景里测试过,只开 Exponential Height Fog 时,远处山峦的过渡非常自然,但走到谷底抬头看树冠,阳光没有体积感。加上 Volumetric Fog 之后,树冠缝隙里的光柱才显现出来。两者叠加时,他通常把 Exponential Height Fog 的密度降到单用时的六成,避免体雾再叠加一次后过曝。

3. Sky Atmosphere

Sky Atmosphere 负责天空球和大气散射。 rays 从太阳进入地球大气,被空气分子和气溶胶散射,才有了蓝天、日落、地平线渐变。

UE5.8 的 Sky Atmosphere 是 physically-based 的,参数直接对应大气成分:

  • Rayleigh Scattering:分子散射,短波长光被散射得更强,所以天空偏蓝。
  • Mie Scattering:气溶胶散射,方向性强,造成太阳周围的光晕和地平线附近的浑浊感。
  • Ozone Absorption:臭氧吸收带,影响日出日落时的红色偏移。

大气透射同样遵循比尔-朗伯定律。对于从地表观察太阳的视线,透射率写为:

Tatm(θ)=eτRayleigh(θ)τMie(θ)τOzone(θ)T_{atm}(\theta) = e^{-\tau_{Rayleigh}(\theta) - \tau_{Mie}(\theta) - \tau_{Ozone}(\theta)}

τ\tau 是沿视线方向的光学厚度,θ\theta 是天顶角。太阳接近地平线时,光程变长,短波长被散射殆尽,剩下的红光居多。

老周做一个海边场景时,把 Mie Scattering Scale 从 1.0 提到 2.5,地平线立刻多了一层暖黄色的浑浊。再把 Rayleigh Scale 微调到 1.2,蓝天饱和度上升,整个画面从塑料感变成了有空气厚度的真实感。

Sky Atmosphere 和 Volumetric Fog 的衔接点在于 Aerial Perspective。远处物体看起来发蓝发灰,就是 aerial perspective 在起作用。UE5 里这个效果由 Sky Atmosphere 自动提供,不需要手动在材质里调雾色。

老周调整 Sky Atmosphere 时习惯从默认资产开始,而不是从零建一个。默认的 SkyAtmosphere Actor 已经连好了太阳角度和强度。先把 Solar Irradiance 调到 1.2,让整体曝光充足,再调 Mie Anisotropy 改变太阳周围光晕的集中度。值越大,光晕越窄越亮,像沙漠里看到的太阳。值小一点,光晕散开,更像湿润的沿海地区。

下面这张图说明了几种雾和大气组件之间的数据关系。

flowchart LR
    A["Sky Atmosphere"] -->|"Aerial Perspective"| B["远景物着色"]
    C["Exponential Height Fog"] -->|"基础密度"| D["Volumetric Fog 体素"]
    E["本地灯光"] -->|"光照注入"| D
    D -->|"体积散射"| F["最终像素"]
    B --> F

4. Volumetric Cloud

云层属于另一个系统:Volumetric Cloud。它用 ray marching 在三维噪声纹理里采样,生成可以飞的云。

老周第一次给开放世界加云时,直接拖了一个 Volumetric Cloud 组件到场景里。默认状态下云层很厚,像一团棉花糖。他调的参数主要有三个:

  • Cloud Layer: 云底高度和厚度。
  • Cloud Material: 默认是 M_VolumetricCloud_Inst,里面连了三维噪声和天气贴图。
  • Sky Atmosphere: 云会被大气光照和阴影影响。

云的渲染开销主要来自 ray marching 步数。步数少会出现条状伪影,步数多会直接吃掉数毫秒。UE5.8 提供了 Volumetric Cloud Ray Marching Step Count 控制台变量,开发时可以把它压低,最终画面再调高。

老周常用的平衡策略是:把主步数设成 128,阴影步数设成 64。这样云本身细腻,投射到地面的阴影也不会太贵。如果镜头主要在地表,不需要仰视天空,他还会把 Cloud Layer 高度上限压低,减少无效采样。

云和体雾最大的区别在于:云是参与光照自遮挡的半透介质,体雾主要描述低密度微粒。云内部可以有强烈的明暗对比,体雾则更均匀。两者在同一场景里共存时,云底边缘可以自然融入大气雾,这个过渡靠 Sky Atmosphere 的 aerial perspective 完成。

如果想让云动起来,可以在云材质里对天气贴图做 UV 动画。老周用一张 R 通道存云覆盖率、G 通道存云类型的贴图,在材质里按时间偏移 UV,再乘上强度参数。这样做比改云组件位置更自然,也不会让云层整体平移露出边缘。

5. 光束与体积光

老周的山洞场景真正打动美术总监的那一刻,是因为一束光。

Directional Light 从裂缝照进来,空气中的微粒被照亮,形成一道可见的光柱。这种效果在 UE 里叫 Volumetric Light Shafts,也叫 God Rays。

实现方式有两种:

  • Light Shaft Occlusion:屏幕空间做法,从光源位置对场景深度做径向模糊。便宜,但只适用于强方向光。
  • Volumetric Fog + Lights:体雾被本地灯光照亮,形成真正的三维光柱。开销更高,但正确性最好。

老周选择了后者。他在裂缝下方放了一个 Spot Light,勾上 Volumetric Scattering Intensity,体雾里的散射强度立刻集中在锥形范围内。参数值不是越大越好,超过 10 之后光柱会变成一根硬棒。老周最后定在 3.5,配合 Scattering Distribution 0.9,边缘有柔和的羽化。

体光的物理本质是相位函数决定散射方向。HG 相位函数写作:

p(θ)=1g24π(1+g22gcosθ)3/2p(\theta) = \frac{1 - g^2}{4\pi (1 + g^2 - 2g \cos\theta)^{3/2}}

gg 就是 Scattering Distribution 参数。g>0g > 0 时前向散射强,适合做光束;g0g \approx 0 时各向同性,适合做雾霾;g<0g < 0 时后向散射强,很少用。

老周还发现一个细节:如果光束从黑暗背景前穿过,但体雾密度太低,光柱会消失。解决办法不是提高整场景雾密度,而是在光束路径上放一个 Volumetric Fog Volume,局部增加密度。这样只影响光束区域,其他地方不受影响。

Volumetric Fog Volume 是一个体积碰撞体,可以覆盖特定区域。老周在洞穴里放了两个,一个覆盖裂缝下方的主光柱,另一个覆盖水面附近的低洼潮气。每个 Volume 的密度、颜色、散射方向都可以独立设置。水面那个 Volume 他把 Albedo 调得偏青一点,营造出潮湿石头的感觉。

6. 与灯光的互动

灯光和雾的互动是大气渲染最容易翻车的地方。灯太少,场景死黑;灯太多,体雾会被切出一块块亮斑。

UE5.8 里,每盏灯光都有一个 Volumetric Scattering Intensity 开关。Point Light 和 Spot Light 默认关闭,Directional Light 默认开启。老周的建议是:只让真正需要参与体雾的灯光开启这个选项。

体雾对灯光数量敏感,不是因为灯光本身贵,而是因为每盏灯都要向体素网格注入光照。灯越多,体素光照计算的负担越重。老周在洞穴里只开了三盏主灯参与体雾,其余补光全部关闭散射。

对于阴影,体雾会使用灯光的阴影贴图。如果 Directional Light 开了 Virtual Shadow Maps,远处体雾的阴影精度会跟着提升。但代价是阴影开销。老周的做法是:只在关键镜头下开启高质量阴影,普通漫游时降低阴影距离。

Spot Light 和 Point Light 参与体雾时,阴影质量尤为重要。没有阴影的体光会变成一盏发光的圆锥,所有方向的雾都被照亮,毫无方向感。老周会在这些灯光上开启 Cast Shadows,并在项目设置里保证 Volumetric Fog 的阴影分辨率不低于 512。分辨率太低,体光边缘会出现方块状的阴影走样。

下面这张时序图展示了灯光、体雾和阴影在渲染一帧时的协作过程。

sequenceDiagram
    participant C as "摄像机"
    participant F as "体积雾体素网格"
    participant L as "灯光"
    participant S as "阴影系统"
    participant P as "最终像素"

    C->>F: 初始化体素网格
    L->>S: 生成阴影贴图
    S->>F: 注入阴影信息
    L->>F: 注入光照强度
    F->>F: 计算散射与消光
    F->>P: 合成到屏幕
    C->>P: 输出最终颜色

7. 性能与精度

体积雾的性能主要由三个因素决定:体素分辨率、灯光数量、采样距离。

在 UE5.8 控制台里,老周常调的是这几个变量:

r.VolumetricFog.GridPixelSize 16
r.VolumetricFog.GridSizeZ 128
r.VolumetricFog.TemporalReprojection 1
r.VolumetricFog.HistoryWeight 0.9

GridPixelSize 越小,屏幕空间体素越细。默认值一般是 8 或 16。老周在高端 PC 上设 8,主机目标设 16,移动端直接关体雾。

GridSizeZ 是深度方向的分层。128 层在近处比较细腻,远处会被指数分布拉远。如果场景主要是中景,64 层就够了。

Temporal Reprojection 是体雾的救命稻草。体素分辨率有限,直接采样会有明显的颗粒感。通过把历史帧的体雾结果混合进来,可以用时间换空间。HistoryWeight 接近 1 时画面很稳,但运动物体后面会拖尾。老周一般在 0.85 到 0.95 之间试。

还有一个容易忽略的参数:体雾的 Scattering DistributionExtinction Scale 会影响近处物体的可见性。密度设高了,前景角色会被雾气吞掉。老周的检查方法是放一个人形 Skeletal Mesh 在镜头前,旋转视角,确认角色轮廓不会被雾吃掉。

性能优化上,老周习惯先做减法:

  1. 减少参与体雾的灯光数量。
  2. 降低体素分辨率一档。
  3. 缩短体雾有效距离。
  4. 关闭 Directional Light 的体积阴影,改用普通级联阴影。

做完这四步,如果帧率还不够,再考虑关掉体雾,用 Exponential Height Fog 兜底。

老周常用的调试命令还有 stat gpu。在 GPU 视图里找到 Volumetric FogVolumetric Cloud 两行,分别看它们占了多少毫秒。如果体雾超过 2 毫秒,他就知道该降分辨率了。如果云层超过 3 毫秒,他会先减 ray marching 步数,而不是直接关云。

8. 平台差异

PC、主机、移动端对大气渲染的支持差距很大。老周的项目要同时上 PC 和移动平台,他必须把这套系统拆成几档。

PC 高配可以全开:Volumetric Fog、Volumetric Cloud、Sky Atmosphere、Volumetric Light Shafts。主机 PS5 和 Xbox Series X 也能开,但要把体素分辨率降一档,云层 ray marching 步数减半。

移动平台最麻烦。很多低端 GPU 不支持 3D 纹理渲染目标,或者体素网格尺寸受限。老周在移动端的做法是:

  • 关闭 Volumetric Fog。
  • 用 Exponential Height Fog 做基础雾。
  • 用 Sky Atmosphere 但关闭 aerial perspective 或者降低采样质量。
  • 云层用平面云贴片或者 Panoramic 天空球代替。

下面这张图对比了不同平台的大气渲染策略。

graph TD
    A["项目需求"] --> B{目标平台}
    B -->|"PC 高配"| C["全开体积雾+云+光束"]
    B -->|"主机"| D["降分辨率+减步数"]
    B -->|"移动端"| E["指数高度雾+简化天空"]
    C --> F["美术效果优先"]
    D --> G["平衡画质与帧率"]
    E --> H["保证可读性"]

不同图形 API 也有细节差异。Vulkan 上体雾的 temporal reprojection 偶尔会出现 jitter,需要在项目设置里确认 Support Temporal Upsampling 开启。DX12 下内存占用比 DX11 高一些,因为 3D 体积纹理在 descriptor heap 里有额外开销。

老周还踩过一个坑:在 Nintendo Switch 上,体积云的默认材质会编译失败,因为某些 noise 节点不支持 half 精度。他最后是手动替换了云材质里的噪声采样节点,才通过打包。

不同图形 API 也有细节差异。Vulkan 上体雾的 temporal reprojection 偶尔会出现 jitter,需要在项目设置里确认 Support Temporal Upsampling 开启。DX12 下内存占用比 DX11 高一些,因为 3D 体积纹理在 descriptor heap 里有额外开销。老周在优化 PC 版本时,会对比 DX11 和 DX12 的帧时间,选择更稳定的那一个,而不是默认认为新 API 一定更快。

写在最后

老周的山洞关卡最终通过了审核。美术总监没有再说话,只是指了指屏幕上那束从裂缝落下来的光。

大气渲染不是炫技。它让场景有纵深、有湿度、有时间的痕迹。清晨的雾、午后的尘、洞穴里的光柱、云层的阴影,这些才是让玩家停下来的理由。

UE5.8 给了足够多的工具,但也需要你分清楚每个系统该干什么。Volumetric Fog 负责空间中的微粒光照,Exponential Height Fog 负责稳定的基础雾效,Sky Atmosphere 负责天空和 aerial perspective,Volumetric Cloud 负责云层。光束是这些系统配合后的自然结果。

做的时候别贪。先把一种雾调对,再叠加下一个。每加一层,就检查一次性能和可读性。玩家不会夸你开了多少特效,只会记得那个阳光穿过尘埃的下午。