很多人看到“假面骑士雨战 《雨爱》”这类二创片段时第一反应是“BGM 选得好氛围感直接拉满”。但如果你做过视频合成、游戏特效或者实时渲染就会知道真正让雨战画面“赢”的从来不是音乐而是那层看起来理所当然的雨景视效。雨战场景是视觉特效里最容易被低估的硬骨头。外行以为“加一层粒子雨就行”内行却知道雨线、水花、湿表面、镜头水痕、雾气、角色衣物浸湿、地面反光这些元素任何一个缺失画面就会立刻从“电影感”跌回“绿幕感”。这篇文章不讨论梗而是把“假面骑士雨战”这种特摄雨戏背后的技术拆开讲清楚从粒子系统到湿表面着色器从屏幕空间水痕到物理交互给出一套可以在 Unity 或类似实时引擎里落地的实现思路。读完你会明白雨战视效的胜负手在于雨不是“下在空气里”而是“下在环境中”。文章会给出可运行的 C# 脚本、Shader 代码、URP 配置思路以及实际项目中容易踩坑的排查清单。1. 雨战场景的技术困局先问一个问题为什么很多独立作品里的雨景一眼假常见的做法是打开粒子系统往场景里喷几千个白色细长粒子然后把粒子速度调高、透明度调低。结果画面看起来像是有无数条白色细线在飘地面没有反应角色衣服没有反应镜头上的雨滴也没有反应。观众说不出哪里不对但就是觉得“这不是雨是故障”。真实雨战场景至少包含四层视觉反馈空中雨线连贯、有速度、有透视深浅变化。地面反馈雨滴落在地面溅起水花积水区域有涟漪和反光。物体表面金属、皮革、布料被打湿后颜色变深、高光变强。镜头反馈雨滴溅到镜头上形成水痕前景模糊光源产生光晕。如果你只做第一层画面必然单薄。假面骑士雨战之所以有临场感是因为特摄团队在拍摄时往往真的用水车制造大雨让雨打在皮套、地面和镜头上产生真实的物理交互。数字环境中要还原这种效果本质上不是“下更多雨”而是“让雨和场景发生关系”。所以这篇文章的核心判断是雨战视效的工程量不在粒子发射器而在材质和交互层。下面按模块拆解。2. 雨视觉的核心构成拆解在动手写代码之前先把“雨”拆成可控的技术单元。2.1 雨线Rain Streaks雨线不是单个水滴而是水滴在视觉暂留中形成的连续线条。实时渲染中通常用粒子系统的“拉伸”或者 Billboard 贴图实现。关键参数不是数量而是三条速度雨滴下落速度通常在 8 到 12 米每秒过慢像雪花过快像 bug。长度粒子拉伸长度与速度、摄像机曝光相关过长会密集到看不清背景。密度纵深雨不是一层均匀的幕布而是有前、中、后三层景深后层稀疏模糊前景偶尔有大雨滴快速掠过。2.2 表面湿润Wet Surface雨中最重要的视觉信号是“材质变湿”。湿表面在物理上有两个明显变化漫反射降低水膜填充了材质的微观凹凸光线不再被粗糙表面散射而是发生镜面反射。结果就是颜色变深。高光增强水膜形成近似镜面的反射层对光源和天空光的反射更加集中。在 Shader 中最直接的做法是引入一个Wetness变量用它混合干燥漫反射和湿润镜面反射。这里有个容易忽略的点金属和皮革的湿效果差异非常大。假面骑士皮套属于高饱和、高光泽材质湿了之后应该呈现“高光压缩 明暗对比加强”的效果而不是简单地整体变黑。2.3 地面水花与涟漪雨点打到地面会溅起细小水花落在积水里会产生涟漪。粒子系统可以做水花但更好的做法是分层大面积积水用平面反射或屏幕空间反射模拟反光。涟漪用延迟生成的贴花或粒子在雨滴落点产生一圈圈扩散圆环。飞溅小而短命的粒子产生后立即向上弹开再落下。一个实用的技巧是使用深度纹理在 Shader 中根据雨滴落点动态生成涟漪法线叠加到地面法线上。这样不需要几千个粒子也能获得密集的雨点反馈。2.4 镜头水痕雨战质感的最后一公里在镜头。真实摄影机镜头被雨淋后会附着稀疏的雨滴和水痕对画面产生局部模糊。屏幕空间后期处理中可以预先烘焙一张水痕纹理按随机位置和大小贴到屏幕空间并让水痕在横向或纵向滑动。这一步很容易做过头。现实中镜头上的雨痕一般不会密集到遮挡全部画面它是“偶尔几滴大的 不断滑落的细痕”。如果做得太满观众会觉得画面脏。2.5 氛围雾气大雨往往伴随着能见度降低原因是雨滴反射和散射光线。可以简单理解为远处的光线在到达摄像机前被大量雨滴散射导致对比度下降、颜色发灰。实时引擎中通常用距离雾、指数高度雾或体积雾实现。雨战场景里雾气不需要很大但需要让背景角色轮廓模糊从而增强前景雨线的纵深感。3. 环境准备与工具链本文的示例基于 Unity 通用渲染管线URP。之所以选 URP是因为它同时适合 PC、移动端和视频渲染且 Shader Graph 和自定义 Shader 都能接入。建议环境如下Unity 2021 LTS 或更新版本URP 12 或更高。渲染管线Universal Render Pipeline。代码方式C# 脚本 HLSL Shader。资源形式粒子系统使用内置 Particle System 或 VFX GraphShader 使用自定义 HLSL 或 Shader Graph。版本细节请以你实际项目为准。本文重点是通用思路不依赖某个特定版本特性。需要准备的场景元素地面至少一片具有法线贴图的平面。角色模型建议使用带有金属和布料材质的测试角色用于观察湿表面差异。灯光至少一盏主光源最好有环境光或天空球。摄像机用于观察前景雨线和屏幕空间效果。4. 雨线粒子系统的落地实现先用一个最小粒子系统把“空中雨线”做出来。4.1 粒子配置思路在 Unity 中新建 Particle System建议参数如下发射方式从 Box 体积发射覆盖摄像机可视范围前、中、后三块区域。粒子数量前层 1500 左右中层 800后层 400不要一次性堆几万个。初始速度范围 8 到 12方向向下稍微加一点 X 和 Z 方向的随机倾斜。粒子大小长条形例如 0.1 宽、0.6 长具体取决于距离。拉伸关闭三维拉伸用 Billboard 贴图加速度拉伸即可。生命周期1 到 2 秒。这里最容易犯的错误是让所有粒子保持完全竖直下落。风中雨线应当有轻微倾斜并且前层粒子的倾斜角度大于后层这样视觉上才有“雨被风吹向镜头”的纵深感。4.2 雨线材质 Shader雨线粒子对 Shader 有两个硬要求一是要支持透视变窄的真实感二是要能叠加亮度而不是简单透明混合。一个简洁的方案是使用“软粒子 加色混合”的 Unlit Shader。Shader Custom/RainStreak { Properties { _MainTex (Rain Texture, 2D) white {} _Intensity (Intensity, Range(0.5, 3.0)) 1.2 _Softness (Soft Particle Factor, Range(0.0, 2.0)) 0.5 } SubShader { Tags { QueueTransparent RenderTypeTransparent RenderPipelineUniversalPipeline } Pass { Blend One One ZWrite Off Cull Off HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl sampler2D _MainTex; float4 _MainTex_ST; float _Intensity; float _Softness; struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; float4 color : COLOR; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float4 color : COLOR; float4 screenPos : TEXCOORD1; }; Varyings vert(Attributes input) { Varyings output; VertexPositionInputs posInputs GetVertexPositionInputs(input.positionOS.xyz); output.positionCS posInputs.positionCS; output.screenPos ComputeScreenPos(posInputs.positionCS); output.uv TRANSFORM_TEX(input.uv, _MainTex); output.color input.color; return output; } half4 frag(Varyings input) : SV_Target { float4 col tex2D(_MainTex, input.uv) * input.color * _Intensity; float2 screenUV input.screenPos.xy / input.screenPos.w; float sceneZ SampleSceneDepth(screenUV); float particleZ LinearEyeDepth(sceneZ, _ZBufferParams); float fade saturate((particleZ - input.screenPos.w) * _Softness); col * fade; return col; } ENDHLSL } } }这段代码做了两件事使用加色混合Blend One One让雨线越密集的地方越亮符合真实雨幕对光线的散射效果。通过场景深度实现软粒子边缘羽化避免雨线在穿过物体时产生生硬切边。跑起来后你应该能看到一层半透明、略带发光感的雨线。如果粒子喷在角色脸上导致明显穿帮可以缩小粒子大小或增加随机偏向。4.3 前后景雨线分层推荐把粒子系统拆成三个独立层背景雨距离镜头 20 米以上粒子大而模糊透明度低。中景雨距离镜头 5 到 20 米粒子适中清晰可见。前景雨距离镜头 1 到 3 米粒子大且速度快偶尔从镜头前掠过。分层的好处是能让摄像机移动时产生视差雨景才有空间感。单层雨线无论调多少参数都像贴在屏幕上的噪点。5. 湿表面 Shader 实现接下来处理“让雨和环境发生关系”的关键湿表面。5.1 简单 PBR 湿度混合在不替换整个项目材质的前提下可以写一个支持湿度的 PBR Shader核心思路是在原有 BRDF 计算中插入一个_Wetness系数。Shader Custom/WetGround { Properties { _BaseMap (Base Texture, 2D) white {} _NormalMap (Normal Map, 2D) bump {} _Smoothness (Smoothness, Range(0.0, 1.0)) 0.5 _Wetness (Wetness, Range(0.0, 1.0)) 0.5 _WetSmoothness (Wet Smoothness, Range(0.0, 1.0)) 0.9 _WetDarkness (Wet Darkness, Range(0.0, 1.0)) 0.6 } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 viewDirWS : TEXCOORD2; }; float _Smoothness; float _Wetness; float _WetSmoothness; float _WetDarkness; Varyings vert(Attributes input) { Varyings output; VertexPositionInputs vertexInput GetVertexPositionInputs(input.positionOS.xyz); output.positionCS vertexInput.positionCS; output.uv TRANSFORM_TEX(input.uv, _BaseMap); output.normalWS TransformObjectToWorldNormal(input.normalOS); output.viewDirWS GetWorldSpaceViewDir(vertexInput.positionWS); return output; } half4 frag(Varyings input) : SV_Target { half4 albedo SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); half3 normalWS normalize(input.normalWS); half3 viewDirWS normalize(input.viewDirWS); float finalSmooth lerp(_Smoothness, _WetSmoothness, _Wetness); half3 finalAlbedo albedo.rgb * (1.0 - _WetDarkness * _Wetness); Light mainLight GetMainLight(); half3 lightDir normalize(mainLight.direction); half ndv saturate(dot(normalWS, viewDirWS)); half3 halfDir normalize(lightDir viewDirWS); half ndh saturate(dot(normalWS, halfDir)); half spec pow(ndh, lerp(32.0, 256.0, finalSmooth)); half3 color finalAlbedo * mainLight.color * saturate(dot(normalWS, lightDir)); color half3(spec, spec, spec) * mainLight.color * _Wetness * 2.0; color finalAlbedo * 0.1; return half4(color, 1.0); } ENDHLSL } } }这是一个示意性的简化 PBR Shader。它没有包含 Unity URP 完整的光照模型但把湿表面的两个核心变化体现出来了湿润后颜色变深finalAlbedo随_Wetness下降。湿润后高光更强且更锐利finalSmooth从原始值向_WetSmoothness靠近同时高光强度乘以_Wetness。把它放到地面上在雨中调低_Wetness为 0再调高为 1对比地面反光效果就能直观体会“材质变湿”的含义。5.2 雨水流痕贴花除了整体变湿真实地面还会有雨流形成的深色水痕尤其在有灰尘、缝隙或墙面的地方。这可以用贴花系统实现在场景中生成若干细长的半透明深色贴花沿重力方向略作扭曲叠加在地面上。贴花 Shader 不需要复杂逻辑只需要在原有光照上增加深色遮罩并降低粗糙度。实践中注意控制贴花数量不要做成“瀑布流”。5.3 涟漪法线叠加地面雨滴涟漪可以用粒子落点的法线贴图扰动实现。思路是用脚本每隔一小段时间随机生成落点。在落点位置绘制一圈环状法线扰动贴花。贴花随时间扩散并淡出。如果觉得实现复杂度高可以先用粒子替代在落点处生成一个扁平圆环粒子缩放从小到大透明度从高到低。这个方法最简单也能获得七成视觉效果。区别在于贴花方式能影响场景原有法线产生真实的反射扰动粒子方式只是叠加半透明纹理在静止地面俯视角下效果较弱。6. 角色与环境的雨水交互假面骑士雨战中最吸引眼球的部分是雨水打在皮套上溅开、顺着装甲缝隙流下的效果。这块在实时渲染里属于进阶内容但值得拆解。6.1 角色表面湿润角色材质不能简单套用地面的湿表面 Shader。对皮套材质来说湿润后的变化更多体现在高光的锐利度和反射强度上。可以在角色的 PBR 材质中增加一个全局WetMask贴图用来控制哪些区域更容易积水。肩膀、头盔顶部雨水直接冲刷湿润度较高。装甲缝隙内侧雨水沿着缝隙流下形成细长水痕。关节内侧、腋下不易直接淋湿湿润度较低。使用 Mask 的好处是避免全身均匀变湿。现实中没有角色会被雨“均匀淋透”。6.2 水滴溅落粒子角色身上的雨滴飞溅可以用局部粒子实现在角色附近添加一个小范围的粒子发射器让粒子在碰撞到角色模型后反弹并散开。Unity 内置 Particle System 的 Collision 模块可以做到这一点但碰撞体数量多时性能压力较大。更廉价的方案是基于屏幕空间的后处理飞溅将“角色与雨的碰撞”简化为“雨滴到达角色表面后产生白色短线条”。这种方案在第三人称视角下够用但在近距离特写中不够精确。如果项目预算允许推荐用 VFX Graph 实现角色表面定向飞溅在粒子系统的Update中检测粒子位置与角色表面距离距离足够近时把粒子速度改为反射方向并缩短生命周期。这是物理上更真实的方案。6.3 雨声与听觉反馈雨战的技术不只有视觉还有听觉。特摄作品中的雨声往往不是单一环境音而是由三层组成底层雨噪持续、均匀的环境雨声提供整体氛围。中层的噼啪声雨水撞到地面、装甲、镜头的随机短促音。顶部空间感带有混响的低频轰鸣模拟大雨覆盖空间室外的感觉。实现上可以使用 Audio Mixer 的 Snapshot 控制雨声强弱当角色进入桥洞或室内时平滑切换。视觉上场景忽然没雨但声音还是外场雨会立刻出戏。7. 完整示例把雨战镜头串起来前面的模块单独看都不复杂真正难的是把它们组合成一个可验证的雨战镜头。下面给出一个最小但完整的流程。7.1 场景搭建步骤创建地面套用WetGroundShader初始_Wetness设为 0。创建三层雨线 Particle System使用RainStreakShader。创建水花 Particle System放在地面高度通过脚本控制随机生成。在摄像机上挂载屏幕空间水痕后处理脚本。在场景中添加一个控制脚本用于全局调节雨量。控制脚本的核心逻辑是用整体RainIntensity参数同步地面湿度、雨线发射速率、水花频率和镜头水痕浓度。using UnityEngine; public class RainController : MonoBehaviour { [Header(Particle Systems)] public ParticleSystem backgroundRain; public ParticleSystem midRain; public ParticleSystem foregroundRain; public ParticleSystem splashParticles; [Header(Renderers)] public Material wetGroundMaterial; public Material characterMaterial; [Header(Post Processing)] [Range(0f, 1f)] public float rainAmount 1f; private ParticleSystem.EmissionModule bgEmission; private ParticleSystem.EmissionModule midEmission; private ParticleSystem.EmissionModule fgEmission; private ParticleSystem.EmissionModule splashEmission; void Start() { bgEmission backgroundRain.emission; midEmission midRain.emission; fgEmission foregroundRain.emission; splashEmission splashParticles.emission; } void Update() { float rain Mathf.Clamp01(rainAmount); bgEmission.rateOverTime Mathf.Lerp(200f, 1500f, rain); midEmission.rateOverTime Mathf.Lerp(100f, 800f, rain); fgEmission.rateOverTime Mathf.Lerp(30f, 250f, rain); splashEmission.rateOverTime Mathf.Lerp(10f, 120f, rain); if (wetGroundMaterial ! null) { wetGroundMaterial.SetFloat(_Wetness, Mathf.Lerp(0f, 1f, rain)); wetGroundMaterial.SetFloat(_Smoothness, Mathf.Lerp(0.3f, 0.9f, rain)); } } }这段脚本的核心是“一辆滑杆控制全局雨量”。你不需要在编辑器里单独调每个粒子系统的参数只调节rainAmount就能从晴天过渡到大暴雨。7.2 运行与验证方法运行场景拖动rainAmount依次观察0 到 0.2地面干燥雨线稀少镜头无水痕属于毛毛细雨。0.3 到 0.5地面明显变湿反光出现雨线连续水花开始产生。0.6 到 0.8地面出现积水反光前景雨线密集角色表面出现高光变化镜头开始偶尔出现水痕。0.9 到 1.0暴雨观感地面大面积反光水花密集镜头水痕明显增多。如果 1.0 时画面没有暴雨感先检查粒子是否被灯光照亮得太均匀以及地面是否过亮。暴雨场景通常需要压低整体环境光雨线才会从背景中跳出来。8. 常见问题与排查思路雨战特效的坑很多下面列几个高频问题。问题现象可能原因排查方式解决方案雨线像白色噪点粒子拉伸不够或速度过慢检查粒子初始速度、拉伸方向和贴图形状提高速度到 8 以上使用细长雨滴贴图雨线穿过角色时切边明显缺少软粒子处理看 Shader 中是否读取场景深度添加深度纹理采样与边缘羽化地面看起来“油腻”而不是“湿润”湿表面高光太强或粗糙度均匀对比 PBR 参数看反光和漫反射的比例降低整体反光强度用 WetMask 控制区域镜头水痕覆盖fullscreen导致画面脏水痕密度过高降低水痕数量与透明度只保留前景细痕和偶尔的大水滴雨景整体偏灰、没有氛围缺少雾气或对比度调节检查场景雾设置增加距离雾压低环境光亮度增加雨线亮度移动端性能严重下降粒子数量过多、实时反射过重用 Profiler 查看粒子与渲染耗时降粒子数改用屏幕空间反射或 planar reflection角色身上的雨滴飞溅不自然简单的碰撞反弹没有延迟检查粒子的生命周期与速度方向缩短飞溅粒子生命周期增加速度衰减VFX Graph 在不同版本间表现不一致版本兼容问题查看项目日志和 shader 报错统一 URP 版本必要时降级用 Particle System9. 最佳实践与工程建议9.1 用全局参数而非逐材质硬编码雨战是典型的“全局天气变化”场景。如果每个材质都直接把湿度写死在 Shader 里后续调节会非常痛苦。建议在场景中使用一个全局 Volume 或脚本作为天气控制器统一驱动所有雨相关参数。一种常见方式是使用 Shader Global VariablesShader.SetGlobalFloat(_GlobalRainAmount, rain);然后在各个 Shader 中通过_GlobalRainAmount采样全局雨量。这样不同的材质可以共享同一个场景湿度状态不需要每个材质单独设置参数。9.2 分层 LOD 控制雨景在移动端的性能风险很高。建议按摄像机距离动态调整粒子层级的密度远景雨层距离远、粒子数量少、尺寸大用低分辨率贴图。中景雨层适当减粒子和粒子更新频率。近景雨层保留较高粒子数量但遮挡概率高需要软粒子。同时地面湿表面 Shader 可以按距离降级远处用简化的镜面高光加噪声扰动近处才启用完整法线扰动和实时反射。9.3 不要滥用屏幕空间反射湿地面反射很容易让人想到屏幕空间反射SSR。SSR 效果真实但性能负担大而且在雨雾场景中因为可见度低反射的细节不一定能被人眼察觉。更稳妥的做法是主光方向反射用简化的高光模型。大面积积水用 planar reflection 或 reflection probe。细节涟漪用法线扰动而不是依赖 GPU 昂贵的 SSR。9.4 录制/直播场景的特殊处理如果你做的是视频向的雨战镜头不是游戏实时操作可以增加一个后期控制脚本把摄像机景深与前景雨粒子的模糊程度绑定。实际视频中前景雨滴在镜头焦点之外是虚化的大光斑。实时游戏里为了清晰度往往会保留锐利雨线但视频输出中适当模糊前景雨会有电影感。一个小技巧在最终输出时叠加一个循环无缝的“雨滴附着镜头”视频层模拟真实摄影机镜片上的水雾和水滴。这在视频剪辑软件里很容易实现比纯 Shader 方案可控性更高。10. 总结与后续实践方向回到开头的问题“假面骑士雨战配《雨爱》会赢吗”真正有意义的回答是BGM 只能锦上添花决定画面是否成立的是雨对场景的渗透程度。雨线、湿表面、水花、镜头水痕、雾气这五层缺一不可。只要地面没有变湿、角色没有高光变化BGM 再催泪画面依然撑不起“雨战”两个字。建议下一步这样实践先用本文的最小场景搭一套雨控制器跑通从晴天到暴雨的过渡。再逐个加入水花、涟漪、镜头水痕、雾气观察每一层对画面质感的增量。最后针对你的目标平台做性能压测找到粒子数量和材质复杂度之间的平衡点。后续可以深入的方向体积雾下的雨线光照、角色表面 WetMask 制作、基于 GPU 的雨滴物理模拟、以及雨声的算法合成。雨战视效是一个综合工程入门靠粒子进阶靠材质真正的分水岭在“雨与世界的互动”。建议把本文收藏备用以后接到雨战、夜战、湿身特效这类需求时可以直接按这个框架拆场景。