做天气系统是我在游戏开发里最喜欢碰的一类需求尤其是下雨天。它不像战斗系统那样逻辑复杂也不像UI那样琐碎但一个高质量的下雨效果能让整个场景的质感瞬间上一个台阶。这篇文章就完整记录一下我在Laya引擎里落地“下雨环境效果”的全过程从方案选型、粒子参数调试、地面交互表现到真机性能优化以及我踩过的那些文档里绝对不会写的坑。先说我做这个需求的初衷那是一个俯视角2.5D项目场景本身偏写实需要一个让玩家一眼就能感受到“天气变了”的氛围层。最开始我的第一反应是偷懒直接找两张雨丝贴图做UV滚动叠加效果出来之后被美术一顿吐槽——雨是“贴在屏幕上”的跟场景完全没有交互感人物走过去裤腿都不带湿的地面溅不起来水花整个画面像是放了一段加了滤镜的视频。所以老老实实重新做用粒子系统配合屏幕Shader和场景联动才拿到了最终这版效果。这篇文章不是教你复制一个万能下雨脚本而是把我在Laya里做这套效果时所有关键节点怎么想、怎么选、怎么调、怎么救回来的全过程写出来。内容比较适合已经会用Laya做基础场景、准备往更高一级的技术美术方向走的开发者阅读新手也可以跳着看参数表和排查清单那两节可以直接抄作业。1. 整体设计思路雨不是“画”上去的是“演”出来的首先要想清楚一个问题生活中真实的雨幕到底由哪几层视觉信息构成的很多人做下雨效果失败就是只抓住了“雨丝”这个单一元素剩下的全丢掉了。我的拆解方式是四层叠加远景雨幕层、近景动态粒子层、地面交互层、屏幕氛围层。远景雨幕是那种半透明的斜线幕布负责在视觉上铺开“整个场景都在下雨”的空间感近景粒子是玩家视野内能看清的雨滴轨迹负责运动和速度感地面交互层是雨落地后的水花溅射、涟漪扩散负责让场景“活”起来屏幕氛围层是镜头上的雨滴划痕和整体偏冷的色调混合负责情绪的收口。这四层缺一不可。只做雨丝没有地面反应雨就像鬼魂在下只做地面反应没有天空幕布场景就会显得干瘪。这个分层思路是后面所有技术实现的总纲。再补一个比较重要的原则这套效果全部跑在客户端本地不需要服务器同步因为天气对所有玩家是一致的是纯表现层的东西。所以开发时自由度很大但也意味着性能压力全在自己这边粒子数量、纹理尺寸、Shader复杂度都要自己扛。1.1 为什么用Laya自带粒子系统而不是纯Shader这是我被问得最多的问题。确实网上有大把用Shader做全屏雨效的教程思路也很清晰屏幕空间射线计算雨滴的透视轨迹、按深度夹取场景遮挡关系效果上限很高。但我的项目是一个大型多人在线游戏角色、场景、特效同屏压力本来就大全屏后处理Shader需要逐像素计算在高端机上是毛毛雨但在中低端安卓机上会直接拖进fps泥潭。所以我选了“粒子为主、Shader为辅”的混合方案所有雨滴的移动、分布、生命周期交给粒子系统这是Laya最成熟的模块异步加载、合批、碰撞都有现成能力和坑可踩Shader只做两件轻量的事——屏幕边缘的雨滴拖尾划痕和地面的漫反射湿润增强而且都用极简实现。对比下来这套方案的上限也许没有纯GPU方案那么“电影级”但胜在可控、稳定、跨设备一致性好。尤其当你面对的是安卓千元机这种下限很低的平台时稳定比上限重要得多。1.2 场景联动不只是视觉还有听感和交互雨的效果除了眼睛能看到耳朵也应该收到信息。这一步是很多团队容易忽略的配了雨声却只做了一段循环音频下大雨和毛毛雨的音量、音色完全没区分场景里的物件和角色也不做任何反应。我的做法是做了三档天气强度小雨、中雨、暴雨。每档对应不同的粒子发射速率、雨声循环切换、音量Lerp过渡时间。在代码里维护一个WeatherState的枚举Notify给粒子控制器和音频管理器用3秒的淡入淡出做切换避免瞬切导致的听觉突兀。同时场景里的非角色元素也要给反应火把加一个受潮概率熄火、水面加一个涟漪粒子发射器、角色身上的武器加一个湿反光参数。这些工作量不大但玩家体验到的“真实感”是靠这些细节堆起来的。1.3 双实现路径2D粒子还是3D粒子Laya引擎里粒子系统分2D和3D两套。2D粒子适合做纯屏幕空间的平面效果比如UI特效、技能拖尾3D粒子是真正的世界空间粒子可以挂在场景节点上随摄像机移动、有真实的深度关系、能和场景模型发生遮挡。我这个项目是俯视角2.5D物理空间是3D的视角是2D化的。所以雨滴必须用3D粒子系统这样才能保证远处的雨和近处的雨透视关系正确。如果强行用2D粒子贴在屏幕上一旦摄像机开始推进或横移雨丝就会像贴纸一样暴露穿帮。补充一个小知识Laya的3D粒子和2D粒子在编辑器里的操作逻辑差别非常大3D粒子的Transform是骨骼动画驱动所有发射器参数都在LayaParticle3D组件里。如果你之前只碰过2D粒子第一次打开3D粒子面板会有劝退感参数多出一个数量级。但别怕真正核心的参数就那么几个后面我会挨个拆。2. 雨丝粒子系统的核心参数解读粒子系统是这个效果的绝对主力参数多如牛毛但真正决定“像不像下雨”的只有几组。我按重要性排了个序新手从第一条开始改就行不要一上来就满面板乱调。2.1 发射器形态为什么用矩形发射器而不是锥形Laya的3D粒子发射器支持球形、锥形、矩形等形状。做雨的时候最自然的直觉是选锥形发射器从天空的一个区域往下喷。但实际跑起来你会发现锥形发射器的初始速度方向是从锥尖向外发散的降落到地面时雨滴的落点会散成一个圆形雨丝之间互相穿插、密度不均匀看起来像下“冒烟”而不是下雨。正确的做法是矩形发射器发射器的长宽覆盖住玩家的视野范围发射方向垂直朝下粒子初速度只在Y轴给一个固定负值X和Z的初速度给一个很小的随机抖动。这样所有雨滴都是平行下落的密度均匀、方向统一视觉上才干净。我踩过一次大坑用锥形发射器做了个看起来还行的小区域雨效但把摄像机拉远后发现雨的范围远不够铺满视野临时改了发射器尺寸结果粒子的世界坐标错乱雨滴从地面往天上飞。最后是把整个发射器参数重置重调的花了一个晚上。2.2 发射速率、生命周期和初速度的黄金组合这三个参数决定了雨的“密度”和“速度感”它们是联动关系的。发射速率决定单位时间生成多少粒子生命周期决定粒子从出生到消失存活多久初速度决定雨滴下落多快。实际操作上我是这样定参数的先定生命周期因为雨滴从发射到落地的时间决定了可见的雨丝长度。摄像机俯视角下视野高度大概20米雨滴下落速度我取35米/秒那么生命周期就是 20 ÷ 35 ≈ 0.57秒。这个值太小雨丝会短成雨点太大会让雨丝穿越地面。初速度设成Y -30到-40的区间X和Z给±1.5的抖动。发射速率调起来更像调音台密度不够画面空密度过头全是糊掉的线。我做的时候是录屏慢放看每一帧的雨丝数量中雨档我调到每秒900个粒子。这个数字看着大但3D粒子单粒子成本很低而且可以依赖粒子系统的合批能力900个粒子并不会压垮CPU前提是你别同时开一大堆别的粒子特效。2.3 纹理制作雨滴贴图的自绘与资源避坑雨丝贴图看起来简单实际翻车率特别高。从网上下载的免费雨滴贴图大多带着黑色背景或者压缩纹理的白色噪点直接导入Laya后粒子周围全是黑色方块效果惨不忍睹。我强烈建议自己画一张雨滴贴图真的只需要一分钟新建一个64×256的画布用渐变工具拉一条白色斜线中间亮度高、边缘完全透明保存为TGA或PNG带透明通道导入时把纹理类型设成Alpha贴图Wrap Mode设Clamp。把粒子渲染的混合模式设成Additive加色混合雨丝就会有一种发光的通透感比Normal混合和背景生硬叠加自然得多。如果实在要用下载的资源那必须确认三件事图片带不带走Alpha通道JPG直接pass、尺寸是不是2的N次幂、压缩格式在目标机型的兼容性。很多“laya模型下载”类的资源包是直接从Unity或U3D项目里扒出来的纹理格式不一定能在Laya Runtime里正常解压这问题查起来非常隐蔽不是画面问题是资源加载时直接报内存错误。3. 地面交互雨滴落地的“最后一厘米”如果说雨丝粒子是画面的骨架地面交互就是让效果从“不错”到“惊艳”的分水岭。玩家根本不会盯着空中的雨看太久他们的视线焦点永远是角色周围一米的位置而那里最抢眼的就是地面溅射。3.1 溅射粒子的实现与生命周期策略地面溅射等于第二套粒子系统而且参数逻辑和雨丝完全不同。溅射粒子的发射点应该落在雨丝粒子死亡的位置附近但粒子系统之间没法直接传递事件坐标所以我用了一个比较朴素的方案在主雨粒子的脚本Update里做碰撞检测一旦检测到地面接触就在接触点生成一个溅射粒子的Play操作。溅射粒子的参数组初速度向上给5~8水平方向随机生命周期0.2~0.4秒粒子Size从0.3衰减到0.05颜色从半透明白渐变成完全透明。数量上不需要和雨丝一一对应每3~5根雨丝触发一次溅射就够既能保持密度又不至于让粒子数翻倍。这里有个小心得溅射粒子不要只做垂直向上的小水珠我叠加了一层贴地的环形涟漪贴图粒子速度更慢、生命周期更长模拟雨水在地面扩散的湿润痕迹。两层叠加之后地面才真的像被雨浸透了。3.2 场景物体的受体反馈怎么让溅射出现在正确的地方溅射粒子最容易出的问题就是穿模和下穿粒子发射出来直接嵌进地面模型里或者在楼梯、台阶这种高低落差位置从侧面穿出去。Laya的粒子系统支持Collision模块但全场景物理碰撞开销太大不能真打物理引擎。我的折中做法场景中所有能落雨交互的地面统一挂一个带Collider的透明体积节点Collider类型用盒体或网格碰撞体Trigger检测打开。粒子检测到碰撞后只做视觉触发不移除粒子而是将粒子速度重置一个向上分量。阶梯和坡道这种特殊位置额外放几个小体积碰撞节点微调效果。这一套跑下来的效果雨滴落在石板路和落在草地上的反应其实是一样的说实话和现实差异挺大。更好的方案是给地面材质设置一个“湿润度”全局Shader参数不同材质对湿润度的响应曲线不同石板更反光、草地更哑光。这个属于后期打磨如果你的主力Shader可控强烈建议加上视觉收益非常大。3.3 一套参数打天下的幻想破灭不同地形各调各的我最初天真地以为溅射粒子一套参数通吃所有地形。后来发现雨滴落在水面上的涟漪效果和落在地面上的溅射效果完全是两个物种。水面要求粒子有扩散波纹感和一定的反射度地面要求粒子有反弹跳跃感。所以我把溅射单独拆成两套WaterSplashEffect挂在所有水面节点的中心用同心圆扩散纹理GroundSplashEffect挂在地面主节点上用随机方向的短促线形粒子。两套共用一个强度控制接口下雨强度变化时统一调节发射速率。4. 屏幕氛围与Shader增强雨丝和地面都有了画面还是差最后一口气差在镜头上。真实雨天你透过窗户或镜头看出去玻璃上会有雨滴痕远处的景物会因为水汽有一点模糊发灰。这些氛围感靠粒子是表现不出来的必须动用Shader。4.1 屏幕雨痕Shader轻量实现与性能取舍玻璃雨痕的常规范式是屏幕空间UV加雨滴法线纹理扰动。听起来很吓人其实实现很轻取一张带多个圆形雨滴的法线贴图在片元着色器里用时间偏移做UV滚动再叠一层噪声作为水滴滑落的路径扰动最后用雨滴法线扰动屏幕UV采样主场景即可。在Laya里这个效果写成一个后处理Shader挂到Camera上。Shader整体就十几行核心代码G-Buffer都不需要只要采样原始屏幕颜色加一个法线贴图。我实测在1080P下跑在骁龙660级别的机器上帧率影响在3帧以内属于可以接受的范围。Shader的具体实现里最容易坑人的是UV滚动速度。雨滴在玻璃上滑落是重力驱动的速度应该是越往下越快是个加速过程而不是匀速滚动。我刚开始图省事用匀速结果雨滴像被匀速拖动的抹布非常假。最后用一个二次函数做高度对应速度的映射效果一下就自然了。4.2 画面调色冷色调和对比度的微调技巧雨天的氛围除了物理表现很大程度靠调色。做雨效时我会给Camera挂一个极简的ColorAdjust组件把色温往冷蓝方向偏移饱和度降低10~15%对比度略微降低让画面有一种“湿度感”。调色的数值不要用固定的要跟着雨强走。暴雨时色温偏得更冷、对比度更低小雨时只做轻微偏移。这块建议在代码里写一个基于WeatherState的插值过渡切换时用Tween补间避免突然跳变让玩家不适。顺带提一个细节雨天场景的雾效也是氛围层的一部分。Laya的3D场景支持指数雾把雾的密度调高、颜色往灰色偏远处场景会被一层水汽笼罩空间深度感立刻拉出来。但注意别把雾开过猛否则性能扛不住画面也会像蒙了一层毛玻璃。4.3 全屏后处理的叠层顺序问题后处理类效果之间是有优先级顺序的。我项目里顺带还挂了Bloom和色差效果曾经把雨痕Shader排到了Bloom之前结果雨痕被Bloom一糊玻璃上全是光斑完全看不到雨滴形态了。最后调整的顺序是场景渲染 - 雨痕扰动 - 雾效混合 - 调色 - Bloom。这个顺序本身并没有绝对标准但建议你把雨痕放在所有“模糊类”效果之前让雨滴边缘保持锐利。5. 实操过程记录从一个空场景到完整下雨效果这一节是复现向的。如果你跟着我上面的设计文档走下面是我实测从零搭到能玩的全流程按步骤操作基本能还原我的效果。5.1 环境准备与Laya版本说明我用的LayaAir 3.0的稳定版本3D模块和粒子系统都是相对成熟的。如果你还在用2.x版本粒子参数面板的命名不太一样需要对照着稍微调整。编辑器导出项目后运行在Chrome做预览真机测试用Android端做性能基线。开始前需要准备资源雨丝纹理一张、溅射水滴纹理一张、涟漪纹理一张、雨滴法线贴图一张。全部64到256量级的小图丢进项目的assets/textures/weather目录。不做图集因为这些贴图只被粒子系统和Shader各自引用一次图集会增加额外的加载和引用复杂性。5.2 创建3D粒子雨丝系统的步骤清单在场景中新建一个3D节点命名为RainRoot挂上LayaParticle3D组件。发射器Shape选Rectangle尺寸设(80, 40, 0)X轴覆盖视野宽度Y轴作为厚度。StartColor的渐变曲线设从纯白到浅白带一点透明模拟远近距离的雨滴亮度差异。StartSize设0.03到0.06雨丝细一点才像雨粗了就是下铁丝。GravityModifier设(-2, -9.8, -2)额外施加一点水平风力和垂直重力让雨有自然的斜飘感。Duration设为InfinityLooping打开。这段配置做完先别急着往下走跑一下看看雨丝密度和方向是不是符合直觉。我第一次就是跳步了结果后面调溅射时才发现雨压根不是垂直下的又重新改了发射器角度。5.3 溅射粒子和脚本控制代码骨架溅射粒子系统建好后需要挂一个自定义脚本控制粒子发射事件。核心代码是检测雨丝粒子位置的地面碰撞并触发溅射示意如下class RainSplashController extends Script { property({type: Particle3D}) splashParticle: Particle3D; private hitPoints: Vector3[] []; onUpdate(): void { // 遍历活动雨粒子检测与GroundCollider的碰撞 let activeParticles this.rainSystem.getActiveParticles(); for (let p of activeParticles) { if (p.position.y groundY) { // 触发溅射 this.splashParticle.transform.position p.position; this.splashParticle.particleSystem.play(); p.reset(); // 回收雨粒子 } } } }注意你看到的这段是简化骨架实际Laya粒子系统的粒子遍历要基于内部的ParticleData数组做遍历不能直接for of否则GC压力会大。建议用一个固定上限的粒子池做复用超过上限时丢掉最老的粒子而不是继续创建。5.4 参数调试的实测经验表调试粒子参数是个痛苦且反直觉的过程。我把最终跑通的一组参数和调试时的观察结果整理成了一个速查表方便你对照着调参数我的最终值调大后果调小后果发射速率900/s画面糊成线团雨稀稀疏疏生命周期0.55s雨丝落地穿透雨丝变短初速度Y-35雨像子弹雨在半空漂浮初速度X/Z抖动±1.5雨像乱刮风雨变成直线粒子Size0.04铁丝雨灰尘雨段数/粒子纹理分割3段雨丝割裂看不出是雨这张表不是玄学每一项背后都有视觉原理。加速度、生命周期和Size共同决定了雨丝在视觉上的“长度”发射速率和视角范围共同决定“密度”抖动参数决定“自然度”。你把数值调成我的终值效果是稳的但不同场景的视野高度和摄像机距离是需要微调的。6. 常见问题与排查实录这篇特意把排查放在最后因为下雨效果调试时出的问题千奇百怪每个都能让人卡半天。我用最典型的几个问题做个速查表并说说背后的排查思路。6.1 粒子穿透地面和穿墙问题问题描述雨丝和溅射粒子在接近地面或墙体时直接穿过或者嵌进模型一半。排查逻辑先看碰撞是否开启。Laya粒子碰撞检测需要CollisionShape有实际碰撞体参与只挂Trigger不行。再看碰撞体的层级粒子节点的Local坐标和场景节点的世界坐标不一致时碰撞检测的坐标转换容易出错。解决方式给地面统一挂碰撞体积粒子碰撞用Shape Query模式而不是物理模拟模式。穿透特别严重的斜坡考虑直接把雨丝粒子生命周期截短到落地点而不是依赖碰撞体拦截。6.2 雨丝闪烁或呈点状断续问题描述雨丝在移动的镜头下闪烁不定或者明明设置了长条纹理却显示成一个个光点。排查逻辑这是贴图过滤和粒子UV动画设置的问题。纹理的MinFilter/MagFilter如果设成了Nearest每帧采样都会跳像素雨丝就闪。粒子组的UV Animation如果开了但贴图没有按帧分割就会采样到空白区域显示成点状。解决方式纹理过滤设成Bilinear关掉粒子的UV Animation让整条纹理始终作为完整雨丝被拉伸。顺带检查一下粒子系统的RenderMode是不是设成了StretchedBillboard如果不是粒子永远显示为贴图原始形状而不是被拉伸的雨丝。6.3 真机性能骤降或发热明显问题描述编辑器里跑得飞起一到真机上就发热、掉帧。排查逻辑编辑器预览用的是PC性能和真机差距巨大。先用Laya Profiler看真机上的粒子Active数量和DrawCall。粒子数量超过2000或者DrawCall超过30基本就是性能瓶颈所在。解决方式把粒子发射速率降下来真机版本我通常会降到编辑器的70%溅射粒子和雨丝粒子合并到同一个纹理图集以减少DrawCall屏幕雨痕Shader在低端机上关闭用一个静态贴图代替。最后给设置面板加一个“极致/标准/流畅”三档画质选项不同档位对应不同粒子密度和Shader开关。6.4 雨声循环切换时的爆音问题描述从小雨切到暴雨音频切换瞬间有一声“啪”的爆音。排查逻辑音频淡入淡出没有做逐帧渐变直接用AudioSource.Stop()再Play()新的clip波形在相位不连续处会生成爆音。解决方式配合WeatherState切换写一个音量管理协程3秒内从当前音量线性Lerp到目标音量期间不切换clip只切换clip前先降低音量到0。或者干脆只用一个雨声循环通过调整音量和EQ模拟不同强度省去切换clip的麻烦。6.5 粒子系统在场景切换时的内存泄漏问题描述切场景后内存不释放连续切几次之后开始卡顿。排查逻辑粒子系统在场景销毁时如果没有主动Stop和Clear粒子数据数组会滞留在内存里等待GC而Unity和Laya的GC策略对托管堆的释放都不及时。解决方式在场景卸载事件里手动调用粒子系统的Stop()并设置Emitter.emit false再把粒子池引用置空。如果项目里经常切场景建议做一个PoolManager统一管理粒子系统的创建和回收而不是每次new一个新的。7. 我个人的一个习惯性收尾小技巧整篇写到这里最后分享一个让下雨效果更容易出彩的小习惯给你的雨效加一个可控的“时间缩放”参数。具体做法是把粒子的SimulationSpeed暴露到运维后台的热更配置里策划可以随时在后台调快调慢雨丝下落速度实时预览效果。我当初是为什么加这个呢因为有次美术提了个需求说要雨“更暴躁一点”我只能火速改代码改参数重新出包。但加了SimulationSpeed之后美术自己拖一下滑杆就能找到合适的“暴躁度”完全不用等程序发版。这个看似不起眼的自由度实际上挽救了好几次本来要通宵改包冲刺的版本。做环境和天气效果技术上真的没有多少高深的东西比的是谁愿意多花时间观察真实世界谁愿意在参数堆里多试几轮谁踩完坑之后愿意把它记下来。希望这篇文章能帮你把已经踩过或者即将踩的坑提前填平让你能把更多时间花在真正让画面变好看的事上。