1.静态雨滴
所谓静态雨滴是体积较小,并不会滑落的水珠,随着时间流逝,体积慢慢变小直到消失。首先,确定静态雨滴所在位置。项目中给出的算法如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#define S(a, b, t) smoothstep(a, b, t) float Saw(float b, float t) { return S(0., b, t)*S(1., b, t); } float3 N13(float p) { float3 p3 = frac(float3(p, p, p) * float3(.1031, .11369, .13787)); p3 += dot(p3, p3.yzx + 19.19); return frac(float3((p3.x + p3.y)*p3.z, (p3.x + p3.z)*p3.y, (p3.y + p3.z)*p3.x)); } float N(float t) { return frac(sin(t*12345.564)*7658.76); } float StaticDrops(float2 uv, float t) { uv *= 40.; float2 id = floor(uv); uv = frac(uv) - .5; float3 n = N13(id.x*107.45 + id.y*3543.654); float2 p = (n.xy - .5)*.7; float d = length(uv - p); float fade = Saw(.025, frac(t + n.z)); float c = S(.3, 0., d)*frac(n.z*10.)*fade; return c; } |
大致思路是将整个Mesh分为40 40块小区域。每个小区域确定一个水珠位置坐标(项目中使用的算法为N13),该区域其它位置到水珠坐标的距离d作为模糊程度的判断依据。例如,项目中S(.3,0., d),即为:d>0.3时,c值为0,后续进行模糊处理的时候为最大模糊程度。

雨滴数量过多时可进行进一步筛选。例如,项目中使用:frac(n.z 10.)删掉一些雨滴。

接着,实现雨滴特征:可以倒影出部分窗外的物体。项目中采取的实现方式是:分别沿水平和垂直方向平移得到两个相同的雨滴,计算该区域的位置分别到这三个雨滴坐标距离的差。相关代码如下:
|
1 2 3 4 5 |
float2 c = Drops(uv, t, staticDrops, layer1, layer2); float2 e = float2(.001, 0.); float cx = Drops(uv + e, t, staticDrops, layer1, layer2).x; float cy = Drops(uv + e.yx, t, staticDrops, layer1, layer2).x; float2 n = float2(cx-c.x, cy-c.x); |
根据上述内容,区域中其它位置到水珠坐标的距离d作为模糊程度的判断依据,越靠近值越大。即:圆心处值为1,圆上圆外值为0。如此根据代码可以得到一个UV值分布图:

如图,黄色轴为v轴,代表u=0的线,为AB线段的中垂线,其上的点到AB两点的距离相同,故差值为0,u=0;同理蓝色轴为u轴,代表v=0的线。这样可以得到相应位置的采样方式,得到效果如图:

在最终采样的时候加上原本的UV坐标即可:
|
1 2 3 |
float4 texCoord = float4(UV.x + n.x, UV.y + n.y, 0, focus); float4 lod = tex2Dlod(iChannel0, texCoord); float3 col = lod.rgb; |
同样的,使用Lerp函数、smoothstep函数进行一些删减,得到效果如图:

2.动态雨滴
所谓动态雨滴是体积较大,会向下滑落,最终移出可视区域的水珠。首先,雨滴形状与分布于上节描述大致相同(12* 2个),代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
float2 UV = uv; uv.y += t *0.75; float2 a = float2(6., 1.); float2 grid = a*2.; float2 id = floor(uv*grid); float3 n = N13(id.x*35.2 + id.y*2376.1); float2 st = frac(uv*grid) - float2(.5, 0); float x = n.x - .5; float y = UV.y*20.; float wiggle = sin(y + sin(y)); x += wiggle*(.5 - abs(x))*(n.z - .5); x *= .7; float ti = frac(n.z); y = (Saw(.85, ti) - .5)*.9 + .5; float2 p = float2(x, y); float d = length((st - p) *a.yx); float mainDrop = S(.4, .0, d); |
区别在于雨滴位置的y值不再利用算法随机生成,而是一个固定值。引入时间变量t改变UV值,产生下落的动画效果。给雨滴位置的x值一个随着y值变化的偏移量wiggle,从而产生摆动效果。
现实中的水珠下落,并不是这样匀速的。而是会停留等待一段时间,等到水珠变大后,加速下落一段距离然后再次停止。项目采用改变雨滴位置的y值来达到这样的效果,给雨滴位置的y值。加上一个偏移量,抵消掉下落距离(uv.y += t * 0.75;),保持雨滴位置不变。代码如下:
|
1 2 3 4 5 6 7 |
float Saw(float b, float t) { return S(0., b, t)*S(1., b, t); } float ti = frac(t + n.z); y = (Saw(.85, ti) - .5)*.9 + .5; float2 p = float2(x, y); |
这样的算法使得ti值为0~ 0.85时,雨滴位置上升,抵消下落距离,ti值为0.85~1时,雨滴位置下降,与下落距离相加,急速下落。
接着实现滑落痕迹,思路与雨滴位置分布类似,根据距离体现出不同的模糊程度即可:
|
1 2 3 4 |
float r = sqrt(S(1., y, st.y)); float cd = abs(st.x - x); float trail = S(.23*r, .15*r*r, cd); trail *= trailFront*r*r; |
最后为了使效果更加逼真,可以再下落的路径中随机产生一些静态雨滴,思路与上节描述类似,代码如下:
|
1 2 3 4 5 6 7 |
float trailFront = S(-.02, .02, st.y - y); y = UV.y; float trail2 = S(.2*r, .0, cd); float droplets = max(0., (sin(y*(1. - y)*120.) - st.y))*trail2*trailFront*n.z; y = frac(y*10.) + (st.y - .5); float dd = length(st - float2(x, y)); droplets = S(.3, 0., dd); |
3.完整代码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
Shader "Custom/Raindrop" { Properties { iChannel0("Albedo (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 Pass{ CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" sampler2D iChannel0; #define S(a, b, t) smoothstep(a, b, t) //#define CHEAP_NORMALS #define HAS_HEART #define USE_POST_PROCESSING float3 N13(float p) { // from DAVE HOSKINS float3 p3 = frac(float3(p, p, p) * float3(.1031, .11369, .13787)); p3 += dot(p3, p3.yzx + 19.19); return frac(float3((p3.x + p3.y)*p3.z, (p3.x + p3.z)*p3.y, (p3.y + p3.z)*p3.x)); } float4 N14(float t) { return frac(sin(t*float4(123., 1024., 1456., 264.))*float4(6547., 345., 8799., 1564.)); } float N(float t) { return frac(sin(t*12345.564)*7658.76); } float Saw(float b, float t) { return S(0., b, t)*S(1., b, t); } float2 DropLayer2(float2 uv, float t) { float2 UV = uv; uv.y += t*0.75; float2 a = float2(6., 1.); float2 grid = a*2.; float2 id = floor(uv*grid); float colShift = N(id.x); uv.y += colShift; id = floor(uv*grid); float3 n = N13(id.x*35.2 + id.y*2376.1); float2 st = frac(uv*grid) - float2(.5, 0); float x = n.x - .5; float y = UV.y*20.; float wiggle = sin(y + sin(y)); x += wiggle*(.5 - abs(x))*(n.z - .5); x *= .7; float ti = frac(t + n.z); y = (Saw(.85, ti) - .5)*.9 + .5; float2 p = float2(x, y); float d = length((st - p)*a.yx); float mainDrop = S(.4, .0, d); float r = sqrt(S(1., y, st.y)); float cd = abs(st.x - x); float trail = S(.23*r, .15*r*r, cd); float trailFront = S(-.02, .02, st.y - y); trail *= trailFront*r*r; y = UV.y; float trail2 = S(.2*r, .0, cd); float droplets = max(0., (sin(y*(1. - y)*120.) - st.y))*trail2*trailFront*n.z; y = frac(y*10.) + (st.y - .5); float dd = length(st - float2(x, y)); droplets = S(.3, 0., dd); float m = mainDrop + droplets*r*trailFront; //m += st.x>a.y*.45 || st.y>a.x*.165 ? 1.2 : 0.; return float2(m, trail); } float StaticDrops(float2 uv, float t) { uv *= 40.; float2 id = floor(uv); uv = frac(uv) - .5; float3 n = N13(id.x*107.45 + id.y*3543.654); float2 p = (n.xy - .5)*.7; float d = length(uv - p); float fade = Saw(.025, frac(t + n.z)); float c = S(.3, 0., d)*frac(n.z*10.)*fade; return c; } float2 Drops(float2 uv, float t, float l0, float l1, float l2) { float s = StaticDrops(uv, t)*l0; float2 m1 = DropLayer2(uv, t)*l1; float2 m2 = DropLayer2(uv*1.85, t)*l2; float c = s + m1.x + m2.x; c = S(.3, 1., c); return float2(c, max(m1.y*l0, m2.y*l1)); } fixed4 frag(v2f_img i) : SV_Target{ float2 uv = ((i.uv * _ScreenParams.xy) - .5*_ScreenParams.xy) / _ScreenParams.y; float2 UV = i.uv.xy; //float3 M = iMouse.xyz / iResolution.xyz; // for now float3 M = float3(0.0, 0.0, 0.0); float T = _Time.y + M.x*2.; #ifdef HAS_HEART T = fmod(_Time.y, 102.); T = lerp(T, M.x*102., M.z>0. ? 1. : 0.); #endif float t = T*.2; //float rainAmount = iMouse.z>0. ? M.y : sin(T*.05)*.3 + .7; // fixed rain amount float rainAmount = M.y; float maxBlur = lerp(3., 6., rainAmount); float minBlur = 2.; float story = 0.; float heart = 0.; #ifdef HAS_HEART story = S(0., 70., T); t = min(1., T / 70.); // remap drop time so it goes slower when it freezes t = 1. - t; t = (1. - t*t)*70.; float zoom = lerp(.3, 1.2, story); // slowly zoom out uv *= zoom; minBlur = 4. + S(.5, 1., story)*3.; // more opaque glass towards the end maxBlur = 6. + S(.5, 1., story)*1.5; float2 hv = uv - float2(.0, -.1); // build heart hv.x *= .5; float s = S(110., 70., T); // heart gets smaller and fades towards the end hv.y -= sqrt(abs(hv.x))*.5*s; heart = length(hv); heart = S(.4*s, .2*s, heart)*s; rainAmount = heart; // the rain is where the heart is maxBlur -= heart; // inside the heart slighly less foggy uv *= 1.5; // zoom out a bit more t *= .25; #else float zoom = -cos(T*.2); uv *= .7 + zoom*.3; #endif UV = (UV - .5)*(.9 + zoom*.1) + .5; float staticDrops = S(-.5, 1., rainAmount)*2.; float layer1 = S(.25, .75, rainAmount); float layer2 = S(.0, .5, rainAmount); float2 c = Drops(uv, t, staticDrops, layer1, layer2); #ifdef CHEAP_NORMALS float2 n = float2(dFdx(c.x), dFdy(c.x));// cheap normals (3x cheaper, but 2 times shittier ;)) #else float2 e = float2(.001, 0.); float cx = Drops(uv + e, t, staticDrops, layer1, layer2).x; float cy = Drops(uv + e.yx, t, staticDrops, layer1, layer2).x; float2 n = float2(cx - c.x, cy - c.x); // expensive normals #endif #ifdef HAS_HEART n *= 1. - S(60., 85., T); c.y *= 1. - S(80., 100., T)*.8; #endif float focus = lerp(maxBlur - c.y, minBlur, S(.1, .2, c.x)); // textureLod to tex2Dlod(ref: https://msdn.microsoft.com/en-us/library/windows/desktop/bb509680(v=vs.85).aspx) //float3 col = textureLod(iChannel0, UV + n, focus).rgb; float4 texCoord = float4(UV.x + n.x, UV.y + n.y, 0, focus); float4 lod = tex2Dlod(iChannel0, texCoord); float3 col = lod.rgb; #ifdef USE_POST_PROCESSING t = (T + 3.)*.5; // make time sync with first lightnoing float colFade = sin(t*.2)*.5 + .5 + story; col *= lerp(float3(1., 1., 1.), float3(.8, .9, 1.3), colFade); // subtle color shift float fade = S(0., 10., T); // fade in at the start //float lightning = sin(t*sin(t*10.)); // lighting flicker //lightning *= pow(max(0., sin(t + sin(t))), 10.); // lightning flash //col *= 1. + lightning*fade*lerp(1., .1, story*story); // composite lightning col *= 1. - dot(UV -= .5, UV); // vignette #ifdef HAS_HEART col = lerp(pow(col, float3(1.2, 1.2, 1.2)), col, heart); fade *= S(102., 97., T); #endif col *= fade; // composite start and end fade #endif //col = vec3(heart); return fixed4(col, 1); } ENDCG } } FallBack "Diffuse" } |