FEATURED · 精选文章

Unity粒子系统碰撞检测与物理水花效果实现

发布时间 / 2026/8/5 15:34:10
来源 / 创域科博编辑部
栏目 / 资讯中心
Unity粒子系统碰撞检测与物理水花效果实现 1. 项目概述当雨滴遇见物理在游戏和交互式媒体中营造沉浸感的关键往往在于那些最细微的细节。想象一下一个阴雨绵绵的场景雨滴打在石板路上不是简单地消失或播放一个预设的动画而是根据撞击的角度、速度真实地溅起大小不一、方向各异的水花甚至在水洼中激起涟漪。这种动态的、由物理规则驱动的视觉效果远比静态贴图或简单粒子发射要生动得多。今天要聊的就是如何在Unity中利用粒子系统结合物理模拟从最基础的雨滴下落开始一步步实现这种令人信服的真实水花效果。这不仅仅是美术效果的制作更是一次程序化内容生成与实时物理的实践。我们将深入粒子系统的核心模块并引入简单的碰撞检测与动力学计算让每一颗“雨滴”粒子都成为一个拥有质量、速度和能量的物理实体。最终目标是构建一个高效、可控且视觉效果丰富的系统它不仅能模拟雨滴还能为其他类似的碰撞溅射效果如火星迸溅、泥浆飞溅提供思路。无论你是技术美术TA、图形程序员还是希望提升作品质感的独立开发者这套方法都能为你打开一扇新的大门。2. 核心思路与系统架构设计实现“雨滴碰撞生成水花”的核心在于将两个相对独立的部分有机结合起来一个是负责模拟大量雨滴的发射与运动的粒子系统另一个是负责处理碰撞事件并生成水花效果的碰撞检测与响应系统。Unity的原生粒子系统功能强大但默认并不为每个粒子提供刚体物理和精确碰撞。因此我们的架构需要一些创造性的设计。2.1 双粒子系统协作方案最直接高效的架构是采用双粒子系统方案雨滴粒子系统 (Emitter System)这是一个标准的粒子系统负责持续或间歇性地发射代表雨滴的粒子。它的核心任务是定义雨滴的外观如拉长的网格或面片、初始速度垂直向下或带些随机倾斜、生命周期以及最重要的——开启碰撞Collision模块。但请注意这里的碰撞并非用于驱动复杂的物理反弹而是作为一个“触发器”和“信息采集器”。水花粒子系统 (Splash System)这是另一个独立的粒子系统通常初始状态为不发射emission率为0。它预配置了水花应有的形态短促的生命周期、飞溅的速度分布、可能的大小变化以及一个快速的淡出效果。它不主动工作而是等待被“唤醒”。这两个系统通过脚本进行桥接。当雨滴粒子通过其Collision模块检测到与场景中的碰撞体如地面、水面Mesh发生碰撞时会触发一个事件。我们在脚本中捕获这个事件获取碰撞点位置、法线方向等信息然后以此为依据命令水花粒子系统在对应位置、朝对应方向通常基于法线反射爆发式地发射一批粒子。为什么选择双系统单系统虽然可能通过子发射器Sub Emitter实现但控制粒度较粗难以根据碰撞特性如角度、速度动态调整水花参数。双系统分离了逻辑使得雨滴和水花的模拟可以独立优化代码也更清晰便于调试和性能管控。2.2 碰撞信息的获取与处理Unity粒子系统的Collision模块可以将碰撞事件发送给一个指定的脚本函数。这是我们整个模拟的“数据源”。我们需要从这个事件中提取几个关键信息position碰撞点的世界坐标。这是生成水花的位置。normal碰撞点所在表面的法线向量。这是决定水花飞溅主要方向的核心依据。velocity粒子在碰撞瞬间的速度向量。其大小magnitude将影响水花的强度、规模和粒子数量。一个常见的优化是不是每次碰撞都立即生成水花。我们可以根据雨滴的速度设置一个阈值过慢的雨滴比如近乎垂直的末端可能不产生明显水花或者只产生少量小水花。这既符合物理直觉也节省了性能。2.3 性能考量与优化起点实时模拟大量粒子的碰撞是性能敏感操作。在架构设计之初就必须考虑碰撞体简化地面、屋顶等接收雨滴碰撞的物体应使用简化的Mesh Collider或甚至Box/Capsule Collider来近似避免使用高精度网格碰撞体。粒子数量控制合理设置雨滴系统的最大粒子数、发射率。水花系统的每次爆发粒子数也应受控避免在暴雨场景下产生“粒子爆炸”。基于距离的裁剪可以实现一个简单的逻辑只在摄像机一定范围内或玩家附近处理高精度的水花生成远景区域使用简化的甚至不生成水花。对象池预置虽然我们使用粒子系统本身管理粒子但对于复杂的水花效果如需实例化Mesh可以考虑对象池来复用GameObject。3. 雨滴粒子系统的详细配置让我们进入实操环节首先搭建雨滴的基底。在Unity中创建一个空GameObject添加Particle System组件我们将其命名为“RainEmitter”。3.1 基础发射与形状设定在Main模块中设置合适的持续时间Duration、循环Looping。发射率Rate over Time根据场景需要设定比如每秒30-50个开始调试。起始速度Start Speed给一个向下的主速度如-15单位/秒并添加一定的随机性Random between Two Constants例如在-12到-18之间模拟风速影响。起始大小Start Size雨滴通常细长。你可以使用“Random between Two Constants”设置一个较小的值范围如0.05, 0.1。更逼真的做法是使用粒子渲染器Renderer模块将渲染模式Render Mode改为Mesh并指定一个自定义的、拉长了的胶囊体或圆锥体网格这样无论从哪个角度看雨滴都有体积感。3.2 重力与外力影响在Velocity over Lifetime模块中可以添加一个恒定的向下加速度模拟重力。但更关键的是External Forces模块如果你有风区Wind Zones可以在这里设置影响力倍增系数Multiplier让雨滴受风影响而倾斜。这会让后续的碰撞计算更富变化。3.3 碰撞模块的核心配置这是雨滴系统的“感官”所在。展开Collision模块类型Type选择“World”。这意味着粒子将与场景中的静态碰撞体交互。碰撞模式Collision Mode选择“3D”。我们是在三维空间中进行模拟。碰撞质量Collision Quality对于雨滴这种小且多的粒子“High”可能会带来较大开销。通常“Medium”甚至“Low”在视觉上已足够因为它主要影响碰撞检测的精度和频率。可以先从“Medium”开始。半径缩放Radius Scale可以略微调小如0.8这相当于缩小了粒子的碰撞体积可以防止过于“敏感”的碰撞检测也更符合雨滴尖端的物理接触点。发送碰撞消息Send Collision Messages必须勾选。这是将碰撞事件传递给脚本的生命线。消亡Collision Death勾选“Die on Collision”。雨滴在碰撞后应立刻消失为生成水花让路。实操心得在复杂场景中你可能不希望雨滴与所有物体碰撞比如微小的装饰品。你可以通过Layer来过滤。在Collision模块底部可以指定粒子与哪些Layer的碰撞体相互作用。为地面、建筑等主要碰撞体分配一个专用Layer如“Ground”并在粒子系统中设置只与该Layer碰撞能有效提升效率和避免意外干扰。4. 水花粒子系统的预配置接下来创建第二个GameObject并添加Particle System命名为“SplashEmitter”。这个系统大部分时间休眠只在收到命令时工作。4.1 发射与基础属性在Main模块中持续时间Duration设为0.1或更短因为我们只需要它短促地爆发一次。循环Looping取消勾选。预热Prewarm取消勾选。起始延迟Start Delay0。起始生命周期Start Lifetime设置一个较短的范围如0.3到0.6秒模拟水花快速消散。起始速度Start Speed这是关键。使用“Random between Two Constants”并设置一个相对较大的范围例如2, 8。水花的速度应比雨滴的末速度要小且方向分散。起始大小Start Size同样使用随机范围如0.02, 0.08。水花粒子通常比雨滴粒子看起来更小、更圆润可以使用小的面片或球形网格。4.2 速度与形状控制Velocity over Lifetime可以在这里给水花粒子一个微小的向下加速度模拟重力对水花的影响使其飞溅轨迹更自然。Shape选择“Sphere”或“Hemisphere”半球。将半径Radius设得非常小比如0.01。这样水花粒子将从碰撞点的一个极小范围内发射形成紧凑的爆发中心。如果使用半球可以调整旋转使其开口朝上沿法线方向更符合水花从表面向上溅射的物理形态。Renderer渲染模式可以选择简单的Billboard广告牌或Horizontal Billboard并使用一个带有渐变或噪声的水花/水滴纹理。使用软粒子Soft Particles可以更好地与地面混合避免硬边。4.3 颜色与消逝Color over Lifetime设置一个从白色或浅蓝色快速过渡到完全透明的渐变模拟水花的消逝。Size over Lifetime可以使用一条曲线让水花粒子在生命中期略微变大再缩小模拟液滴在空中的形态变化。配置完成后在Inspector中确保这个粒子系统的Play On Awake是取消勾选的。我们将通过脚本控制它的播放。5. 桥接脚本碰撞检测与水花生成现在我们需要编写C#脚本来连接两个系统。创建一个名为RainCollisionSplash的脚本并将其附加到“RainEmitter”游戏对象上。5.1 脚本结构与变量声明using UnityEngine; public class RainCollisionSplash : MonoBehaviour { // 指向水花粒子系统的引用 public ParticleSystem splashParticleSystem; // 控制水花生成强度的参数 public float minSplashSpeedThreshold 5.0f; // 产生水花的最小速度阈值 public int baseParticleCount 5; // 基础粒子数 public float speedToCountFactor 0.5f; // 速度对粒子数量的影响系数 public float maxParticleCount 20; // 单次最大粒子数限制 // 用于临时控制水花发射的变量 private ParticleSystem.EmitParams emitParams; void Start() { if (splashParticleSystem null) { Debug.LogError(Splash Particle System not assigned!); return; } // 初始化发射参数结构体 emitParams new ParticleSystem.EmitParams(); // 确保水花系统初始处于停止状态可选依赖Inspector设置 splashParticleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } }5.2 碰撞事件处理函数Unity粒子系统在勾选Send Collision Messages后会调用挂载在同一个GameObject上的脚本中的特定函数。我们需要实现这个函数// 这个函数名是固定的由粒子系统调用 private void OnParticleCollision(GameObject other) { // 获取当前粒子系统的所有碰撞事件 ParticleSystem ps GetComponentParticleSystem(); int numCollisionEvents ps.GetCollisionEvents(other, collisionEvents); // 使用一个可重用的列表来存储碰撞事件需在类中声明 // private ListParticleCollisionEvent collisionEvents new ListParticleCollisionEvent(); // 遍历所有碰撞事件 for (int i 0; i numCollisionEvents; i) { ParticleCollisionEvent collisionEvent collisionEvents[i]; Vector3 collisionVelocity collisionEvent.velocity; float speed collisionVelocity.magnitude; // 速度阈值判断过慢的雨滴不产生水花 if (speed minSplashSpeedThreshold) continue; Vector3 collisionPoint collisionEvent.intersection; Vector3 surfaceNormal collisionEvent.normal; // 计算水花的主要飞溅方向基于法线的反射方向并添加随机性 Vector3 reflectDir Vector3.Reflect(collisionVelocity.normalized, surfaceNormal); // 让反射方向更偏向法线方向即更向上并添加随机扰动 Vector3 splashDir Vector3.Slerp(reflectDir, surfaceNormal, 0.3f); splashDir Quaternion.Euler( Random.Range(-30f, 30f), Random.Range(0f, 360f), Random.Range(-30f, 30f) ) * splashDir; splashDir.Normalize(); // 根据碰撞速度动态计算本次水花应发射的粒子数量 int particleCount Mathf.FloorToInt(baseParticleCount speed * speedToCountFactor); particleCount Mathf.Min(particleCount, maxParticleCount); // 限制上限 // 配置发射参数 emitParams.position collisionPoint; emitParams.velocity splashDir * Random.Range(speed * 0.1f, speed * 0.2f); // 水花速度是雨滴速度的一小部分 emitParams.startSize Random.Range(0.02f, 0.05f); emitParams.startLifetime Random.Range(0.2f, 0.5f); // 发射水花粒子 splashParticleSystem.Emit(emitParams, particleCount); } }关键点解析Vector3.Reflect用于计算理想反射方向但真实水花并非完美镜面反射。我们使用Vector3.Slerp将其部分混合到表面法线方向surfaceNormal并添加随机旋转这样得到的水花方向更分散、更自然更像液体撞击后破碎飞溅的效果。粒子数量与速度挂钩模拟了动能越大、溅射越剧烈的物理现象。5.3 场景组装与参数调试将脚本挂载到“RainEmitter”对象。在Inspector中将“SplashEmitter”对象拖拽到脚本的splashParticleSystem字段。确保场景中作为地面的物体拥有ColliderMesh Collider或Box Collider。运行游戏。你应该能看到雨滴下落撞击地面后消失并在撞击点生成一小簇向上飞溅的水花粒子。现在进入最关键的环节参数调试。minSplashSpeedThreshold调高此值只有速度更快的雨滴才会产生水花场景会更“干燥”调低则更“湿润”甚至毛毛雨也有水花。speedToCountFactor和maxParticleCount控制水花规模的丰富度。暴雨场景下你可能需要提高baseParticleCount和speedToCountFactor但务必用maxParticleCount限制峰值防止性能骤降。水花速度计算splashDir * Random.Range(speed * 0.1f, speed * 0.2f)中的系数0.1, 0.2决定了水花飞溅的力度。系数越大水花溅得越高越远。方向随机性Quaternion.Euler中随机角度的范围-30f, 30f等控制水花的扩散锥角。范围越大水花越分散。调试是一个观察、调整、再观察的过程。建议在Scene视图中开启Gizmos显示粒子并放慢时间尺度Time Scale以便仔细观察每一次碰撞和生成的水花形态。6. 进阶优化与效果增强基础系统运行起来后我们可以从多个维度提升其真实感和性能。6.1 基于碰撞材质的不同响应现实世界中雨滴打在泥土、水面、石板或树叶上产生的水花效果是不同的。我们可以实现一个简单的材质系统。创建一个脚本SurfaceMaterial定义水花规模、粒子颜色、是否产生涟漪等属性并将其附加到不同的地面物体上。在OnParticleCollision函数中通过collisionEvent.colliderComponent获取被撞击物体的SurfaceMaterial组件。根据获取到的材质参数动态调整emitParams中的startSize、startColor甚至切换不同的水花粒子系统预设。// 在OnParticleCollision循环内 SurfaceMaterial surfMat collisionEvent.colliderComponent?.GetComponentSurfaceMaterial(); if (surfMat ! null) { emitParams.startColor surfMat.splashColor; particleCount Mathf.FloorToInt(particleCount * surfMat.splashIntensityMultiplier); // 如果材质要求播放声音可以在这里触发 // AudioSource.PlayClipAtPoint(surfMat.impactSound, collisionPoint); }6.2 涟漪效果的集成水花常伴随着涟漪。我们可以使用一个简单的平面网格配合一个动态修改UV偏移和透明度的Shader来实现。创建一个预设Prefab包含一个带有涟漪动画Shader的圆面片。在RainCollisionSplash脚本中维护一个对象池来管理这些涟漪预设。当产生水花时同时从对象池中取出一个涟漪实例放置于碰撞点可能需要沿法线方向轻微上移并初始化其动画如设置初始大小、开始淡出。涟漪动画结束后将其放回对象池。这增加了视觉层次感尤其对于水面或潮湿表面效果显著。6.3 性能深度优化策略当雨滴数量极大或需要多角色、多场景使用时性能成为焦点。事件合并OnParticleCollision可能每帧被调用很多次。可以考虑在Update中累积碰撞点然后在LateUpdate中合并处理比如将一定半径内的多个碰撞点合并为一个“强化”的水花发射减少发射调用次数。层级细节LOD根据水花与摄像机的距离动态调整其粒子数量、是否启用涟漪效果。距离远的可以只发射1-2个粒子甚至不发射。GPU粒子考量对于极大规模的雨滴成千上万可以考虑使用Unity的Visual Effect GraphVFX Graph来制作基于GPU的粒子系统。VFX Graph能处理更大量的粒子并且其碰撞和事件触发逻辑虽然设置不同但原理相通性能潜力更大。不过它需要兼容的渲染管线URP/HDRP和一定的学习成本。7. 常见问题与排查实录在实际操作中你几乎一定会遇到下面这些问题。这里是我的排查笔记。7.1 问题雨滴穿模不触发碰撞可能原因1碰撞体缺失或层级错误。检查地面物体是否有Collider组件并且该Collider的Layer是否包含在雨滴粒子系统Collision模块的Layer Mask中。可能原因2粒子速度过快。在单帧内粒子从碰撞体上方直接运动到了下方错过了碰撞检测。可以尝试增加碰撞体厚度。在粒子系统的Collision模块中提高Collision Quality为“High”这会增加每帧的检测次数。略微降低雨滴的起始速度。可能原因3粒子碰撞半径太小。在Collision模块中适当增大Radius Scale例如从1.0调到1.2。7.2 问题水花位置偏移或方向奇怪可能原因1碰撞点坐标转换问题。确保emitParams.position直接使用的是collisionEvent.intersection这是世界坐标。如果水花系统是某个子物体需要确认其坐标空间。可能原因2法线方向错误。在Scene视图中Debug绘制法线检查collisionEvent.normal是否垂直于表面。如果模型法线有问题可能需要调整导入设置或使用带碰撞的简单代理物体。可能原因3方向计算过于随机。减少Quaternion.Euler中的随机角度范围让水花方向更集中地沿反射方向。7.3 问题水花粒子发射延迟或堆积可能原因水花系统播放状态管理冲突。我们的脚本使用的是Emit()方法它不依赖系统的播放状态。但如果错误地使用了Play()可能会导致时序混乱。确保脚本逻辑清晰只在需要时Emit不要频繁调用Play()/Stop()。如果水花粒子生命周期结束后仍有残留检查水花系统的Stop Action是否设置为Destroy或Disable。7.4 问题性能随雨量增加急剧下降排查步骤使用Unity Profiler的CPU模块观察OnParticleCollision和ParticleSystem.Emit的耗时。如果OnParticleCollision耗时高尝试提高碰撞检测的阈值minSplashSpeedThreshold减少无效事件处理。如果Emit耗时高严格限制maxParticleCount并考虑使用上文提到的“事件合并”优化。检查单帧内水花粒子的峰值总数。在粒子系统的Main模块中查看Particle Count。如果数字持续很高需要降低水花粒子的生命周期或发射数量。7.5 问题水花效果“纸片感”强不立体解决方案将水花粒子的渲染模式从Billboard改为Mesh并使用一个低面数的球体或变形球体网格。为水花粒子使用一个包含折射、高光反射的Shader模拟水的质感。URP/HDRP中的Lit或Simple Lit Shader Graph可以快速实现。添加微弱的点光源如场景中的路灯在水花爆发位置瞬间亮起并快速衰减模拟水花对光线的散射能极大增强立体感和动态感。调试这类交互式特效耐心和细致的观察是关键。多使用Debug DrawLine来可视化速度、法线方向在Game视图和Scene视图之间切换放慢时间一步步验证你的逻辑和数据流。每一个参数背后都对应着物理世界的一种特性调整它们就是在微调你创造的这个小世界的物理法则。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻