
1. 项目概述与环境烟雾的核心价值在游戏和实时渲染领域环境氛围的营造往往决定了玩家沉浸感的深度。一片弥漫在古老森林中的晨雾一座工业城市上空挥之不去的烟霾或是战场废墟上尚未散尽的硝烟这些看似“背景”的元素实则是连接虚拟世界与玩家感官的关键桥梁。过去实现这类效果要么依赖昂贵、静态的贴图或体积雾要么就需要美术师耗费大量精力手绘序列帧不仅效率低下动态交互更是无从谈起。Unity的粒子系统正是打破这一僵局的利器。它允许我们通过程序化控制海量微小的“粒子”来模拟这些自然或人为的、动态变化的视觉效果。今天要深入探讨的“环境烟雾”就是粒子系统一个极具代表性的高阶应用。它不仅仅是简单的“一团雾”而是一个需要综合考虑物理模拟、视觉艺术和性能优化的综合系统。一个逼真的环境烟雾需要模拟出烟雾的生成、扩散、上升、消散的全过程并且能与场景中的风场、光源甚至玩家角色产生合理的互动。为什么我们要花大力气研究它因为一个高质量的环境烟雾系统能直接提升项目的视觉品质和叙事能力。在恐怖游戏中它可以制造未知的恐惧在奇幻RPG里它能烘托神秘的气氛在模拟驾驶中它让尾气和环境尘埃更加真实。掌握其进阶技巧意味着你不仅能实现“有烟雾”更能实现“有灵魂的烟雾”。2. 环境烟雾的核心设计思路与模块拆解打造动态逼真的环境烟雾不能一上来就埋头调参数。我们需要先建立一个清晰的物理和视觉模型理解烟雾从产生到消失的整个生命周期中哪些特性是需要被模拟的。这能帮助我们有条不紊地配置粒子系统的各个模块。2.1 物理特性模拟烟雾的行为逻辑真实的烟雾行为遵循一些基本的物理规律我们的粒子系统需要从以下几个维度去逼近生成与初始状态烟雾通常从一个或多个源点如烟囱、火堆、排气管产生。初始时粒子密集、速度较快、尺寸较小且相对不透明。上升与扩散由于热空气密度较低烟雾会自然上升浮力。同时烟雾分子会与周围空气混合导致其体积不断膨胀、密度降低这个过程就是扩散。在粒子系统中我们通过给粒子施加一个向上的力Force over Lifetime来模拟浮力通过让粒子尺寸随时间增大Size over Lifetime来模拟视觉上的扩散。消散与透明化随着扩散的进行烟雾粒子与空气充分混合其浓度和可见度会逐渐降低直至完全不可见。这通过粒子颜色主要是Alpha通道随时间衰减Color over Lifetime来实现。受环境影响烟雾会被风吹动会围绕障碍物流动在室内和室外的表现也不同。这需要我们结合Unity的风区Wind Zones、物理碰撞Particle System Collision模块甚至自定义力场来模拟。2.2 视觉特性模拟烟雾的“质感”物理模拟保证了行为的合理性视觉模拟则决定了它看起来像不像“烟”或“雾”。材质与着色器这是定义烟雾视觉风格的基础。通常我们会使用一个具有Alpha通道的半透明纹理如噪波图、云状图并配合特定的着色器如Unity标准的Particles/Standard Unlit或更复杂的体积粒子着色器。纹理的质量决定了烟雾的细节和“脏净”程度。光照与阴影环境烟雾如何与场景光照交互是简单地忽略光照Unlit还是接受环境光Lit高质量的烟雾可能需要接受柔和的环境光并在特定角度产生一定的透光效果散射这通常需要自定义着色器或后处理来辅助。颜色与密度变化烟雾的颜色并非一成不变。刚产生的烟可能更黑、更浓消散时则更灰、更淡。靠近光源的烟雾可能被“照亮”。这需要通过Color over Lifetime模块甚至根据粒子与光源的距离动态调整颜色来实现。2.3 粒子系统模块化配置策略基于以上分析我们可以将Unity粒子系统的模块进行功能分组发射控制组Emission发射率、Shape发射器形状。决定烟雾从哪里、以何种速率和形态产生。对于环境烟雾发射器形状可能是Sphere模拟区域弥漫、Cone模拟点源向上或Mesh附着在复杂物体上。生命周期控制组Main模块中的Start Lifetime生命周期、Start Speed初速。Size over Lifetime尺寸变化、Color over Lifetime颜色/透明度变化。这是模拟烟雾扩散和消散的核心。动力学控制组Velocity over Lifetime速度变化、Limit Velocity over Lifetime限速、Force over Lifetime受力用于模拟浮力、风力。Noise噪波让运动更随机自然。Collision碰撞让烟雾绕过障碍物。渲染控制组Renderer模块指定材质和着色器。这里是决定最终视觉质感的门户。一个高效的配置流程是先通过“发射控制组”确定烟雾源再用“生命周期控制组”塑造其基本形态变化接着用“动力学控制组”赋予其物理行为最后用“渲染控制组”打磨视觉外观。注意不要试图一次性调好所有参数。建议遵循“从简到繁”的原则先关闭所有高级模块只打开Emission、Shape、Main和Renderer调出一个静态的、基础的烟雾团。然后再逐个启用并调试Size over Lifetime、Color over Lifetime等模块每步都观察变化确保理解每个参数的影响。3. 从零构建一个动态森林晨雾的实战案例理论说得再多不如动手做一遍。让我们以一个具体的需求为例为一片森林场景创建清晨时分在地面弥漫、会随风缓缓飘动、并随着时间或玩家靠近逐渐消散的动态晨雾。3.1 第一步创建基础粒子系统与发射器在Unity场景中右键 - Effects - Particle System创建一个新的粒子系统。将其重命名为“Forest_Morning_Fog”。选中该粒子系统我们先重置参数点击Inspector面板右上角的三个点选择Reset确保从一个干净的状态开始。配置Main模块Duration: 设置为50秒。环境雾是持续存在的我们需要一个很长的持续时间。Looping: ✔ 勾选。让雾循环持续。Start Lifetime: 设置为20。粒子会存在20秒这给了它足够的时间去飘散和消散。Start Speed: 设置为0.1。晨雾几乎是静止的只有非常缓慢的移动。Start Size: 设置为3。初始尺寸较大模拟一片连续的雾团。Max Particles: 设置为1000。这是系统同时存在的最大粒子数。雾需要一定的密度1000是个合理的起点后续根据性能调整。配置Emission模块Rate over Time: 设置为30。每秒生成30个新粒子。这个速率结合20秒的生命周期可以维持约600个粒子30*20在我们1000的上限内能形成稳定的雾层。配置Shape模块形状选择Box。我们希望雾覆盖一个区域而不是从一个点发出。将Box的Scale设置为 (50, 1, 50)。这定义了一个长宽各50米高度仅1米的扁平盒子。雾将主要在这个扁平区域内生成和存在模拟贴地的晨雾。此时运行游戏你应该能看到一个静止的、持续生成的白色粒子方块。这还完全不像雾但我们已经搭建好了“舞台”。3.2 第二步赋予烟雾形态与生命周期配置Size over Lifetime模块勾选启用该模块。将曲线从默认的恒定值调整为一个从0.5到1.5的上升曲线。点击曲线区域在横轴时间0到1的0处将纵轴尺寸乘数设为0.5在横轴1处将纵轴设为1.5。这意味着粒子在出生时尺寸是初始尺寸的一半在生命结束时变为1.5倍。这模拟了烟雾在空气中扩散、体积变大的过程。配置Color over Lifetime模块勾选启用该模块。这是实现烟雾“消散”感的关键。点击颜色条打开渐变编辑器。我们需要编辑的是Alpha通道透明度。将渐变条左侧的Alpha值对应粒子出生时设为150约60%透明度#96FFFFFF。在渐变条中间偏右的位置比如时间0.7处添加一个关键点将Alpha值设为80约31%透明度。将渐变条最右侧的Alpha值对应粒子死亡时设为0完全透明。同时你也可以微调颜色。将左侧颜色设为灰白色#E6E6E6FF右侧颜色设为更淡的灰白色#F0F0F0FF模拟烟雾变淡的过程。现在粒子应该会从小变大同时从半透明逐渐变得完全透明最后消失。已经有了烟雾动态的雏形。3.3 第三步添加物理行为与随机性静止的雾不自然我们需要让它“活”起来。配置Velocity over Lifetime模块勾选启用。将空间Space设置为Local。这样方向是基于粒子系统自身的坐标系。将Z值蓝色轴通常代表前向设为0.5。这给了粒子一个向前的轻微速度。结合我们扁平的Box发射器这个“前向”是在水平面内的可以模拟一个基础的飘动方向。配置Force over Lifetime模块勾选启用。Space选择World。浮力是世界空间中的向上力。将Y值设为0.2。这是一个很弱的向上的力模拟晨雾轻微的上升趋势。如果力太大雾就会飞走不像贴地雾了。配置Noise模块Unity高级粒子功能勾选启用。这是让烟雾运动变得自然、有机的神器。Strength: 设置为0.5。控制噪波影响的强度。Frequency: 设置为0.2。降低频率让运动变化更平滑不至于抖动得太厉害。勾选Separate Axes可以分别控制XYZ轴的强度。例如可以将Y轴的强度Strength Y调低至0.2让垂直方向的扰动小一些保持雾的整体贴地感。配置Renderer模块这是最后一步也是决定视觉风格的一步。我们需要一个合适的材质。在Project窗口右键 - Create - Material创建一个新材质命名为“Fog_Material”。将材质的Shader改为Particles/Standard Unlit。对于不接收复杂光照的雾这个着色器效率高且效果足够。在Surface Options中将Blending模式从Opaque改为Alpha Blend这是半透明效果的标准混合模式。我们需要一张烟雾纹理。你可以从Unity的Standard Assets中导入Assets Import Package Particles使用其中的Smoke纹理或者从Asset Store下载更高质量的烟雾贴图。将纹理拖到材质的Base Map上。将Fog_Material拖拽到粒子系统Renderer模块的Material属性上。在Renderer模块中将Sorting Fudge值调低例如-100。这个值影响粒子的渲染排序调低它有助于让雾粒子在大多数其他半透明物体如树叶后面渲染避免穿插错误。至此一个基本的、动态的森林晨雾就完成了。你可以播放场景看到雾在地面区域生成、缓慢飘动、扩散并最终消散。4. 进阶优化提升真实感与性能基础效果有了但离“逼真”还有距离。下面是一些进阶技巧能让你的环境烟雾脱颖而出。4.1 实现与风场的交互静态的风力设置不够动态。Unity有全局的Wind Zone组件但粒子系统默认不直接受其影响。我们需要通过脚本将风力的影响传递给粒子。在场景中创建一个Wind ZoneGameObject - 3D Object - Wind Zone调整其方向和大风强度。为“Forest_Morning_Fog”粒子系统对象添加一个C#脚本例如FogWindAffector.csusing UnityEngine; [RequireComponent(typeof(ParticleSystem))] public class FogWindAffector : MonoBehaviour { private ParticleSystem ps; private ParticleSystem.ForceOverLifetimeModule forceModule; public WindZone windZone; // 在Inspector中拖入WindZone对象 public float windInfluence 0.5f; // 风力影响系数 void Start() { ps GetComponentParticleSystem(); forceModule ps.forceOverLifetime; // 如果没指定WindZone尝试查找场景中第一个 if (windZone null) windZone FindObjectOfTypeWindZone(); } void Update() { if (windZone ! null) { // 获取风区当前的风向世界空间 Vector3 windDirection windZone.transform.forward * windZone.windMain; // 将风力叠加到粒子系统的Force over Lifetime上注意模块是Local空间 // 这里简单处理将世界风力转换到粒子的局部空间。更复杂的做法可能需要考虑粒子位置。 Vector3 localWind transform.InverseTransformDirection(windDirection) * windInfluence; forceModule.x localWind.x; forceModule.y localWind.y; forceModule.z localWind.z; } } }这个脚本会动态读取风区的强度和方向并将其转换为一个力施加在粒子的Force over Lifetime模块上从而实现雾随风向飘动的效果。4.2 使用自定义着色器实现体积感标准的粒子着色器渲染的是一个个“面片”Billboard在侧面看会穿帮缺乏体积感。我们可以使用更高级的着色器技术。一种常见方法是使用Particles/VertexLit Blended着色器并配合一张带有径向渐变中心亮边缘暗的圆形纹理。这能让每个粒子看起来更像一个柔和的“体积球”而不是一个扁平的片。更高级的做法是编写自定义着色器实现视差偏移或光线步进但这属于图形学高级主题对性能影响也较大。对于大多数移动端或性能敏感的项目优化好的面片粒子配合优秀的纹理和动画已经足够。4.3 LOD多层次细节与性能管控环境烟雾可能覆盖很大范围对性能挑战巨大。必须实施LOD策略。距离裁剪在粒子系统的Main模块中使用Max Particle Size参数。它可以限制远处粒子的最大尺寸防止它们因透视投影变得过大而过度消耗填充率。简化远距离烟雾创建两个或三个不同复杂度的粒子系统预制体。高细节版使用完整的Noise、Collision模块高分辨率纹理用于近处。中细节版关闭Collision降低Noise强度使用较低发射率用于中距离。低细节版关闭Noise和Force over Lifetime使用非常简单的材质极低的发射率和粒子数用于远处。编写LOD脚本根据摄像机与烟雾源的距离动态启用/禁用或替换不同细节等级的粒子系统。Unity的LOD Group组件通常用于Mesh对于粒子系统需要自定义脚本管理。烘焙静态雾效对于完全静态、不受交互影响的远景雾可以考虑使用雾效贴图烘焙到地形上或者使用Unity的全局体积雾Volumetric Fog如果项目管线支持如HDRP/URP。这能极大减少实时粒子数量。4.4 与场景的深度交互让烟雾绕过树木、岩石等障碍物能极大提升真实感。启用粒子系统的Collision模块。将Type设置为World这样粒子会与场景中的碰撞体交互。将Mode设置为3D实现立体碰撞。调整Dampen阻尼、Bounce反弹等参数。对于烟雾通常我们希望它碰到障碍物后速度减慢并沿着表面滑行所以Dampen可以设高如0.8Bounce设低如0.1。确保场景中你希望烟雾绕开的物体如树干、岩石拥有Collider组件。实操心得粒子碰撞是性能消耗大户。务必谨慎使用并确保碰撞体尽可能简单使用Box或Sphere Collider避免复杂的Mesh Collider。最好只为关键的大型障碍物启用碰撞对于细小的草丛等可以忽略。5. 常见问题排查与性能调优实录在实际项目中调试粒子效果就像在驯服一头视觉野兽。以下是我踩过的一些坑和解决方案。5.1 问题烟雾看起来像一堆硬边片片不柔和可能原因1纹理问题。使用的纹理边缘太硬或者Alpha通道过渡不自然。解决使用专业的、带有柔和边缘的烟雾/云朵纹理。在Photoshop中检查纹理的Alpha通道确保边缘是渐变的噪波而不是清晰的形状。可能原因2粒子排序错误。粒子之间渲染顺序错乱导致片片相互穿透看起来有硬边。解决在粒子系统Renderer模块中尝试调整Sorting Fudge值。也可以尝试将Render Alignment从View改为World但这可能会让粒子在非正对摄像机时变形。最根本的方法是确保粒子材质使用正确的Queue如Transparent并考虑使用自定义渲染排序。可能原因3粒子尺寸过大或过小。过大的粒子片会暴露其平面本质过小则密度不够无法形成连续感。解决调整Start Size并配合Size over Lifetime曲线让粒子在生命中期尺寸最大出生和死亡时较小。同时增加粒子数量Max Particles和发射率Rate over Time用更多的小粒子来构建更连续的体积。5.2 问题烟雾性能开销巨大导致帧率下降可能原因1粒子数量过多。这是最常见的原因。解决严格遵守“用最少的粒子达到最好的效果”原则。首先尝试降低Max Particles和Rate over Time。通常通过优化Size over Lifetime和Color over Lifetime曲线让粒子在“看不见”的阶段如完全透明时尽快结束生命周期可以有效减少同时存在的粒子数。可能原因2使用了复杂的着色器或过高的纹理分辨率。解决为烟雾粒子使用尽可能简单的着色器如Particles/Standard Unlit或Particles/Alpha Blended。将纹理压缩为合适的格式如ASTC 4x4 for Mobile并降低分辨率512x512通常足够。可能原因3启用了昂贵的模块。Collision、复杂的Noise尤其是High Quality模式、Sub Emitters子发射器等模块非常消耗性能。解决按需启用。远景的烟雾绝对不要用碰撞和高质量噪波。考虑用脚本模拟简单的群体运动来代替部分Noise效果。诊断工具使用Unity的Profiler分析器和Frame Debugger帧调试器。在Profiler的Rendering区域查看Draw Calls和SetPass Calls烟雾是否导致它们激增在Frame Debugger中查看每一帧渲染了哪些粒子数量是否合理5.3 问题烟雾在移动设备上闪烁或显示异常可能原因Alpha混合与深度缓冲Z-Fighting。半透明物体的渲染顺序依赖提交顺序而非深度容易产生闪烁。解决确保粒子材质的ZWrite是关闭的对于半透明物体通常如此。尝试在粒子系统Renderer模块中开启Occlusion遮挡选项但这可能会增加开销。最实用的方法是让烟雾渲染在大多数不透明物体之后但在天空盒之前。这可以通过调整摄像机Depth或使用多个摄像机层Layer并设置不同的Clear Flags和Depth来实现。例如将烟雾渲染到一个单独的、深度更高的摄像机。避免让烟雾粒子与地形或其他复杂网格表面过于接近保持一定距离。5.4 问题如何让烟雾与游戏逻辑如玩家进入交互解决方案使用粒子系统触发器或脚本。方法A粒子系统Trigger模块。可以设置当粒子进入某个碰撞体时触发一个事件如播放声音、销毁粒子、改变颜色。你可以将一个带有Collider的物体放在玩家角色上当玩家走进雾中时触发粒子消失或变淡的效果。方法B脚本控制。这是更灵活的方式。例如你可以写一个脚本检测玩家与烟雾发射器的距离动态调整粒子系统的Emission Rate或Start Lifetime。// 一个简单的脚本示例当玩家靠近时烟雾变淡降低发射率 using UnityEngine; public class FogInteraction : MonoBehaviour { public Transform player; // 玩家Transform public ParticleSystem fogParticleSystem; public float maxDistance 20f; // 最大影响距离 public float minEmissionRate 5f; public float maxEmissionRate 30f; private ParticleSystem.EmissionModule emissionModule; void Start() { if (fogParticleSystem ! null) emissionModule fogParticleSystem.emission; } void Update() { if (player ! null fogParticleSystem ! null) { float distance Vector3.Distance(player.position, transform.position); float rate Mathf.Lerp(minEmissionRate, maxEmissionRate, distance / maxDistance); emissionModule.rateOverTime rate; } } }调试环境烟雾是一个需要耐心和观察力的过程。记住一个黄金法则永远在目标平台尤其是移动设备上进行最终测试。在Editor里流畅的效果在真机上可能完全是另一回事。通过持续的迭代、性能分析和艺术感觉的打磨你最终能创造出既震撼又高效的环境烟雾系统为你的虚拟世界注入真实的呼吸感。