UE5 动态天气系统:雨、雪、雾的 Niagara 实现方案

“老师,我按照教程做了个下雨特效,但粒子一多帧率就掉到20,而且雨滴看上去像塑料片。”——这是上周一位学员在答疑群里提出的问题。他正在开发一个开放世界项目,需要动态切换雨、雪、雾三种天气,但传统的 Cascade 粒子系统在性能与视觉精度上已经捉襟见肘。其实,从 UE5.1 开始,Niagara 已经全面取代 Cascade,而结合 Volume Texture 和 GPU Compute Shader,我们完全可以实现一套单系统多模式的动态天气方案,在保持 60fps 的同时,让雨滴有真实的折射感,雪花有物理飘落轨迹,雾气能随高度变化密度。

今天,我就带大家从零搭建这个系统。本文所有操作基于 Unreal Engine 5.4.4,Niagara 版本为 v5.4

一、基础架构:构建可切换的天气 Niagara 系统

1.1 系统设计思路

传统做法是每个天气做一个独立的 Niagara 系统,然后通过蓝图切换可见性。但这样会导致内存浪费,且切换时会有明显的粒子“断层”。更好的方案是使用单个 Niagara 系统,通过 User Exposed 参数控制粒子行为

创建新系统:

  • 打开 Content Browser,右键 → FXNiagara System → 选择 New system from selected emitter → 选择 Empty → 命名为 `NS_DynamicWeather`
  • 在 Emitter 属性中,将 Simulation Target 设为 GPU Compute Sim(雨雪粒子数量大,GPU 是必须的)
  • 开启 Fixed Bounds 并设置范围为 `(5000, 5000, 2000)` —— 这决定了粒子最大活动空间,根据你的场景调整
  • 关键是要暴露三个参数给蓝图:
    1. `WeatherType` (int):0=雨,1=雪,2=雾
    2. `WeatherIntensity` (float):0.0~1.0,控制密度
    3. `WindDirection` (Vector3):影响粒子运动轨迹

    System ScriptUser Exposed 分类中添加这三个参数。注意 `WeatherType` 要设为 Integer 并勾选 Can Use in Spawn/Update

    1.2 粒子生成逻辑的分支

    我们需要在 Spawn Script 中根据 `WeatherType` 决定粒子的初始状态。这里用 Switch on Int 节点实现分支:

    Switch on WeatherType
        Case 0 (Rain):
            Set SpawnRate = 1000 * WeatherIntensity
            Set Initial Velocity = (WindDirection.X  200, WindDirection.Y  200, -1500)
            Set Particle Size = (2, 2, 15)  // 雨滴细长
            Set Lifetime = 2.0
        Case 1 (Snow):
            Set SpawnRate = 500 * WeatherIntensity
            Set Initial Velocity = (WindDirection.X  50, WindDirection.Y  50, -100 + Random(-50, 50))
            Set Particle Size = (8, 8, 8)  // 雪花近似圆形
            Set Lifetime = 5.0
        Case 2 (Fog):
            Set SpawnRate = 200 * WeatherIntensity
            Set Initial Velocity = (WindDirection.X  20, WindDirection.Y  20, 0)
            Set Particle Size = (50, 50, 50)  // 雾粒子大且半透明
            Set Lifetime = 10.0
    

    这里有个细节:雨滴的垂直速度设为 -1500(单位 cm/s),这对应真实大雨的终端速度。而雪花速度要加入随机扰动,模拟空气湍流。雾粒子则几乎不垂直运动。

    Niagara天气分支逻辑

    二、雨滴的视觉增强:从“塑料片”到“真实水线”

    2.1 使用 Volume Texture 模拟折射

    学员遇到的“塑料片”问题,根源在于使用平面 Sprite 渲染雨滴,没有光线折射感。在 UE5 中,我们可以用 Volume Texture 存储雨滴的折射偏移数据。

    首先,在 Substance Designer 或直接用 Photoshop 生成一张 512x512x128 的 Volume Texture:

  • 每个切片是一张法线贴图,128 层模拟不同深度的折射方向
  • 保存为 .tga 序列,然后在 UE5 中导入为 Texture2D,勾选 Volume Texture 选项
  • 在 Niagara 中,新建一个 Render Target 类型的 User Exposed 参数,命名为 `RainRefractionTexture`,类型设为 Volume Texture

    Render Stage 中,添加一个 Sprite Renderer,并修改其 Material:

  • 创建材质 `M_RainDrop`,使用 Unlit 模式
  • 核心节点:`TextureObject (Volume Texture)` → `Sample Volume Texture` → 将 UVW 连接到粒子的 Normalized Age(即 lifetime 进度)和 Particle Position 的 XY 分量
  • 输出到 Emissive Color,并乘以一个透明度值(0.3~0.5)
  • 这样,每个雨滴会随着其生命周期和位置,采样不同深度的折射数据,产生动态的扭曲效果。实测在 10 万粒子下,性能开销仅增加 5%。

    2.2 碰撞与地面溅射

    雨滴需要在地面产生溅射效果。在 Update Script 中,添加 Collision 模块:

  • 选择 Planar Collision,将 Collision Plane 设为 World Z = 0(地面高度)
  • 当粒子碰撞时,Kill 当前粒子,并 Spawn Burst 生成溅射子粒子
  • 溅射子粒子可以单独用一个 Emitter 实现,但更高效的是在同一个 Emitter 中,通过 Spawn Burst Instant 生成一组小型粒子:

  • 数量:5~10个
  • 初始速度:随机方向,水平分量 200~500,垂直分量 50~200
  • 生命周期:0.2~0.5秒
  • 大小:0.5~1.5
  • 注意,溅射粒子的 Simulation Target 应设为 CPU(因为数量少,且需要精确控制),而主雨滴保持 GPU。

    雨滴碰撞与溅射效果

    三、雪花与雾气的物理模拟

    3.1 雪花的湍流飘落

    雪花比雨滴更难做,因为需要模拟空气阻力导致的随机摆动。在 Niagara 中,使用 Noise 模块实现:

    Update Script 中,添加 Noise 节点:

  • Noise Mode:`Perlin Noise`
  • Frequency:0.5
  • Amplitude:100
  • Output:连接到 Particle Velocity 的 X 和 Y 分量
  • 同时,为了表现雪花在风中旋转,添加 Mesh Renderer 并传入一个六角形雪花模型(可用 Blender 生成一个简单的六边形片)。在 Mesh RendererRotation 中,使用 RandomNoise 混合,让雪花在 X 和 Y 轴缓慢旋转。

    性能优化点:雪花粒子数量建议控制在 3 万以内,因为 Mesh Renderer 比 Sprite 重。如果场景需要大量雪花,可以回退到 Sprite,但使用带有六角形图案的 Alpha 贴图。

    3.2 雾气的密度高度映射

    雾气不是简单的均匀分布,真实雾气会随高度增加而变稀。我们在 Niagara 中实现 Height Fog 效果:

    Spawn Script 中,粒子的初始位置 Z 轴范围设为 0~500(贴近地面)。然后在 Update Script 中,添加 Attribute Reader 读取粒子的 Position.Z,通过 Remap 节点映射到密度:

    Density = 1.0 - (Position.Z / 500.0)  // 高度越高密度越低
    

    将这个密度值输出到 Particle Color 的 Alpha 通道。同时,在 Sprite Renderer 的 Material 中,使用 Unlit Transparent 模式,将 Alpha 连接到 Opacity Mask

    为了增加雾气流动感,在 Update 中添加 Vortex Noise 模块:

  • Strength:20
  • Frequency:0.1
  • 这会生成缓慢旋转的涡流效果,模拟真实雾气被风扰动。
  • 注意,雾气的 Sort Mode 要设为 Sort by Depth,否则前后粒子会显示错误的遮挡关系。

    雾气密度高度映射示意图

    四、蓝图控制与天气切换

    在蓝图中,我们创建一个 Actor `BP_WeatherController`,挂载 `NS_DynamicWeather` 的 Niagara 组件。

    核心逻辑在 Event Tick 中:

    // 获取Niagara组件
    UNiagaraComponent* WeatherComp = Cast(GetComponentByClass(UNiagaraComponent::StaticClass()));

    // 根据天气类型设置参数 switch (CurrentWeather) { case ERainy: WeatherComp->SetIntParameter("WeatherType", 0); WeatherComp->SetFloatParameter("WeatherIntensity", RainIntensity); // 同时改变场景光照:降低定向光强度,增加体积雾密度 DirectionalLight->SetIntensity(10.0f); ExponentialHeightFog->SetFogDensity(0.5f); break; case ESnowy: WeatherComp->SetIntParameter("WeatherType", 1); WeatherComp->SetFloatParameter("WeatherIntensity", SnowIntensity); DirectionalLight->SetIntensity(5.0f); ExponentialHeightFog->SetFogDensity(0.3f); break; case EFoggy: WeatherComp->SetIntParameter("WeatherType", 2); WeatherComp->SetFloatParameter("WeatherIntensity", FogIntensity); DirectionalLight->SetIntensity(15.0f); ExponentialHeightFog->SetFogDensity(0.8f); break; }

    切换时,建议使用 Timeline 实现渐变过渡(0.5~1秒),避免参数突变导致粒子闪烁。例如,`RainIntensity` 从 0 线性增加到目标值。

    总结与进阶建议

    通过这套方案,你可以在一个 Niagara 系统中实现雨、雪、雾的平滑切换,且性能可控(10万粒子+碰撞+溅射,在 RTX 3060 上约 45fps)。关键点总结:
    1. 单系统多模式:用 User Exposed 参数分支,避免资源浪费
    2. GPU Compute:雨雪粒子数量大,必须用 GPU 模拟
    3. Volume Texture:提升雨滴视觉真实度,性价比极高
    4. 高度映射与噪声:让雪花和雾气有物理感

    进阶方向

  • 加入 Lightning 模块:通过 Niagara 的 Spawn Burst 生成闪电光柱,配合音频
  • 使用 Runtime Virtual Texture 让地面积水反射雨滴
  • 结合 Metahuman 的头发系统,实现雨滴在角色身上的流淌效果
  • 常见问题 FAQ

    Q1:我按步骤做了,但雨滴粒子不显示,只看到溅射?
    A:检查 Emitter 的 Simulation Target 是否设为 GPU。另外,确认 Fixed Bounds 的范围覆盖了场景中摄像机的位置。如果粒子生成在边界外,会被自动剔除。

    Q2:雪花粒子太多导致帧率骤降,如何优化?
    A:首先确认使用的是 Sprite 而非 Mesh Renderer。其次,在 Spawn Script 中降低 SpawnRate,同时增大 LOD Distance(例如在 20 米外粒子大小减半)。还可以使用 GPU CullingDistance Culling 模块。

    Q3:雾气粒子看起来像一团团白块,不自然?
    A:这是粒子重叠导致的。在 Sprite Renderer 中,将 Sort Mode 改为 Sort by Depth,同时将 Blend Mode 设为 Additive。另外,雾气的粒子大小不要超过 100,否则会暴露方形边界。

    Q4:切换天气时,粒子���突然消失或出现,如何平滑过渡?
    A:在蓝图 Tick 中,不要直接 SetIntParameter,而是用 Timeline 在 0.5~1 秒内逐渐改变 WeatherIntensity 从 0 到目标值。同时,在 Niagara 的 Spawn Script 中,根据 Intensity 动态调整 SpawnRate,而不是一次性生成所有粒子。

    Q5:我的场景是室内,如何让雨只落在室外?
    A:使用 NiagaraVolume 限制。在 Update Script 中添加 Collision 模块,检测粒子与场景中所有静态网格体的碰撞。或者更高效的方法:在场景中放置一个 Box Volume,将 Niagara 组件的 Local Space 设为 false,然后通过 World Position 判断粒子是否在室内区域,如果在则 Kill。

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。