UE5.8 体积雾与大气:从山洞晨雾到云端日落
老周负责的关卡做到第三周,美术总监盯着屏幕看了半天,只回了一句话:这山洞缺口气。
画面里模型精度够高,贴图也是 PBR 流程,灯光色温也调过。可整个场景就是平的,像一张干净的布景。老周把 Directional Light 调暗又调亮,补了几盏 Point Light,结果更糟,洞壁被切成了几块明暗分明的硬面。
缺的那口气,就是大气。雾气、尘埃、湿度、高空散射,这些东西本来不存在于模型里,却决定了场景有没有呼吸感。UE5.8 里处理这类效果的系统有好几个,名字长得像,功能边界也容易混淆。这篇文章把老周踩过的坑拆开讲一遍。
1. Volumetric Fog 基础
Volumetric Fog 是 UE5 处理三维空间雾气的核心方案。它把整个视锥体切成一个体素网格(Voxel Grid),在每个体素里计算散射、吸收和光照贡献,最后把结果合成到屏幕上。
老周第一次打开 Volumetric Fog 时,场景立刻有了体积感。光束从洞顶裂缝漏下来,空气中悬浮的微粒被照亮,边缘柔和。这种效果靠传统贴图平面雾做不出来,因为平面雾只改变像素颜色,不参与光照。
体雾的渲染流程可以画成下面这张架构图。
flowchart TD
在每个体素内部,引擎执行的是辐射传输方程的简化版。光线穿过介质时,强度按比尔-朗伯定律衰减:
其中 是消光系数, 是沿路径的密度分布。 表示从距离 处返回的眼睛方向上,光线还剩多少没被介质吸收或散射掉。
散射到眼睛的光由下面这个积分描述:
这里 是散射系数, 是从光源方向进入该点的入射光。实际引擎不会逐像素做这个积分,而是先在体素里采样,再用临时体积纹理缓存,最后重投影和滤波。
体雾有几个关键参数老周建议直接记住:
- 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 是更老牌的方案。它假设雾密度随高度指数下降,计算极快,也极容易控制。很多户外场景仍然靠它打底。
密度公式很直接:
是地面基准密度, 是高度衰减尺度。 越小,雾越贴近地面; 越大,雾气向上铺得越开。
老周在山谷场景里同时开了 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:臭氧吸收带,影响日出日落时的红色偏移。
大气透射同样遵循比尔-朗伯定律。对于从地表观察太阳的视线,透射率写为:
是沿视线方向的光学厚度, 是天顶角。太阳接近地平线时,光程变长,短波长被散射殆尽,剩下的红光居多。
老周做一个海边场景时,把 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 --> F4. 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 相位函数写作:
就是 Scattering Distribution 参数。 时前向散射强,适合做光束; 时各向同性,适合做雾霾; 时后向散射强,很少用。
老周还发现一个细节:如果光束从黑暗背景前穿过,但体雾密度太低,光柱会消失。解决办法不是提高整场景雾密度,而是在光束路径上放一个 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.9GridPixelSize 越小,屏幕空间体素越细。默认值一般是 8 或 16。老周在高端 PC 上设 8,主机目标设 16,移动端直接关体雾。
GridSizeZ 是深度方向的分层。128 层在近处比较细腻,远处会被指数分布拉远。如果场景主要是中景,64 层就够了。
Temporal Reprojection 是体雾的救命稻草。体素分辨率有限,直接采样会有明显的颗粒感。通过把历史帧的体雾结果混合进来,可以用时间换空间。HistoryWeight 接近 1 时画面很稳,但运动物体后面会拖尾。老周一般在 0.85 到 0.95 之间试。
还有一个容易忽略的参数:体雾的 Scattering Distribution 和 Extinction Scale 会影响近处物体的可见性。密度设高了,前景角色会被雾气吞掉。老周的检查方法是放一个人形 Skeletal Mesh 在镜头前,旋转视角,确认角色轮廓不会被雾吃掉。
性能优化上,老周习惯先做减法:
- 减少参与体雾的灯光数量。
- 降低体素分辨率一档。
- 缩短体雾有效距离。
- 关闭 Directional Light 的体积阴影,改用普通级联阴影。
做完这四步,如果帧率还不够,再考虑关掉体雾,用 Exponential Height Fog 兜底。
老周常用的调试命令还有 stat gpu。在 GPU 视图里找到 Volumetric Fog 和 Volumetric 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 负责云层。光束是这些系统配合后的自然结果。
做的时候别贪。先把一种雾调对,再叠加下一个。每加一层,就检查一次性能和可读性。玩家不会夸你开了多少特效,只会记得那个阳光穿过尘埃的下午。