最终效果

实现思路
网格
首先使用一个Unlitshader,为了限制雨滴的移动范围,需要先定义出网格,首先将采样得到的纹理坐标乘一个数,然后取小数部分,就可以得到重复的网格:
1 | fixed4 frag (v2f i) : SV_Target |

雨滴
使用smoothstep,通过计算到网格中心的距离来画出雨滴:
1 | float drop = smoothstep(0.05, 0.03, length(grid_uv / aspect)); |

移动逻辑
额外定义两个变量来控制雨滴偏移的量,通过改变这个两个变量来实现雨滴的移动,直接对网格坐标做加减即可:
1 | float x = 0; |
然后就是最重要的,正常情况下雨滴落在窗户上,会先缓慢下滑,然后快速下滑,先使用一个函数模拟这个过程:

为了避免雨滴出现上滑,所以再同时把网格的y往下移,就可以做出这样的视觉效果:不断有雨滴从缓慢下滑到快速下滑:
1 |
|
雨滴轨迹
思路就是同样在生成drop的同时生成几个比它小的drop:
1 | float2 trailPos = (grid_uv - float2(x, t * 0.25)) / aspect; |

在这个基础上再去做颜色的渐变:
1 | trail *= smoothstep(0.5, y, grid_uv.y); |

然后就是雨滴滑落的过程中的左右摇晃运动,描述函数:

1 | float w = i.uv.y * 10; |

随机化
为了让每个雨滴的轨迹,下落时间不一样,还需要分离每一个格子,让每个格子都有一个独有的id号,然后就可以针对不同的格子去做随机:
1 | float2 grid_id = floor(uv); |

格子有了不同的颜色,即有了不同的id。然后根据这个float2的id去生成一个随机数:
1 | float GetRandom(float2 p) |
成功给每个格子生成了一个随机数:

然后就能用每个格子生成的随机数去设置这个格子里水滴的移动(左右和上下):
1 | float gridValue = GetRandom(grid_id); |

这样就已经有了水滴随机下滑的效果。
雾气效果
1 | float fogTrail = dropPos.y > 0 ? 1 : 0; |

采样
然后就是拿这个图去对窗户后面的景色进行采样:
1 | float2 offsets = drop * dropPos + trail * trailPos; |
然后就已经可以得到很棒的效果了:

为了做到模糊的效果,使用tex2Dlod进行采样:
1 | float2 offsets = drop * dropPos + trail * trailPos; |

雨滴层级
使用一个for循环去控制多层级的雨滴,数值越大,雨滴越多:
1 | float3 drops = DrawDrop(i.uv, t); |
窗户效果
要真正有着透过窗户看到外面的效果,可以使用grabpass实现。为了正确得到镜子后面的效果,必须让它最后渲染,所以把它的队列为Transparent,在不透明物体渲染完之后再渲染它。计算出抓取texture的坐标后用tex2Dproj进行采样,就可以得到正确的透视效果:
1 | struct v2f |

模糊效果
模糊效果的思路和做高斯模糊相似,直接在采样的时候对坐标进行偏移,然后采样后做均值就行:
1 | proj_uv += drops.xy * _Distortion;//控制雨滴程度 |

现在已经是挺不错的效果了。
Mipmap Level
现在有一个比较明显的问题是当距离比较远的时候,水滴看起来会很像闪烁着的点:

这是因为当相机离目标物体比较远时,目标物体只占屏幕很小一块区域(pixels),但我们还是用了正常尺寸的texture对目标物体进行渲染,此时一个pixel对应多个texel,采样就会不足。所以要基于距离的远近去做细节的程度,于是使用fwidth函数,会返回这个值在当前像素和它的下一个相邻像素之间的差值(与X和Y方向上的下一个像素上该值差的绝对值和)。
我们可以把这个差值放大,然后把它限制在0~1之间,把它作为一个系数去控制雨滴的细节程度:
1 | float fade = 1 - saturate(fwidth(i.uv) * 50); |
这样就不会出现很远的距离还能看见一个一个的雨滴了:

OVER!