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,右键 → FX → Niagara System → 选择 New system from selected emitter → 选择 Empty → 命名为 `NS_DynamicWeather`
关键是要暴露三个参数给蓝图:
1. `WeatherType` (int):0=雨,1=雪,2=雾
2. `WeatherIntensity` (float):0.0~1.0,控制密度
3. `WindDirection` (Vector3):影响粒子运动轨迹
在 System Script 的 User 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),这对应真实大雨的终端速度。而雪花速度要加入随机扰动,模拟空气湍流。雾粒子则几乎不垂直运动。
—
二、雨滴的视觉增强:从“塑料片”到“真实水线”
2.1 使用 Volume Texture 模拟折射
学员遇到的“塑料片”问题,根源在于使用平面 Sprite 渲染雨滴,没有光线折射感。在 UE5 中,我们可以用 Volume Texture 存储雨滴的折射偏移数据。
首先,在 Substance Designer 或直接用 Photoshop 生成一张 512x512x128 的 Volume Texture:
在 Niagara 中,新建一个 Render Target 类型的 User Exposed 参数,命名为 `RainRefractionTexture`,类型设为 Volume Texture。
在 Render Stage 中,添加一个 Sprite Renderer,并修改其 Material:
这样,每个雨滴会随着其生命周期和位置,采样不同深度的折射数据,产生动态的扭曲效果。实测在 10 万粒子下,性能开销仅增加 5%。
2.2 碰撞与地面溅射
雨滴需要在地面产生溅射效果。在 Update Script 中,添加 Collision 模块:
溅射子粒子可以单独用一个 Emitter 实现,但更高效的是在同一个 Emitter 中,通过 Spawn Burst Instant 生成一组小型粒子:
注意,溅射粒子的 Simulation Target 应设为 CPU(因为数量少,且需要精确控制),而主雨滴保持 GPU。
—
三、雪花与雾气的物理模拟
3.1 雪花的湍流飘落
雪花比雨滴更难做,因为需要模拟空气阻力导致的随机摆动。在 Niagara 中,使用 Noise 模块实现:
在 Update Script 中,添加 Noise 节点:
同时,为了表现雪花在风中旋转,添加 Mesh Renderer 并传入一个六角形雪花模型(可用 Blender 生成一个简单的六边形片)。在 Mesh Renderer 的 Rotation 中,使用 Random 和 Noise 混合,让雪花在 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 模块:
注意,雾气的 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. 高度映射与噪声:让雪花和雾气有物理感
进阶方向:
—
常见问题 FAQ
Q1:我按步骤做了,但雨滴粒子不显示,只看到溅射?
A:检查 Emitter 的 Simulation Target 是否设为 GPU。另外,确认 Fixed Bounds 的范围覆盖了场景中摄像机的位置。如果粒子生成在边界外,会被自动剔除。
Q2:雪花粒子太多导致帧率骤降,如何优化?
A:首先确认使用的是 Sprite 而非 Mesh Renderer。其次,在 Spawn Script 中降低 SpawnRate,同时增大 LOD Distance(例如在 20 米外粒子大小减半)。还可以使用 GPU Culling 的 Distance 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:使用 Niagara 的 Volume 限制。在 Update Script 中添加 Collision 模块,检测粒子与场景中所有静态网格体的碰撞。或者更高效的方法:在场景中放置一个 Box Volume,将 Niagara 组件的 Local Space 设为 false,然后通过 World Position 判断粒子是否在室内区域,如果在则 Kill。

评论(0)