先丢一个演示视频:https://www.bilibili.com/video/BV1E5411R7az

  整体来说,是根据 https://b23.tv/ZapyRQy 这个教程学习的。Shader的雨点是纯数学运算的,目前没有特别理解其中的奥妙,只是觉得太牛了。源Shader来自于ShaderToy。

  触摸擦除雨点,是记录了触摸的轨迹,然后Tick的时候,维护轨迹的生命周期,并对点附近的像素提供颜色权值贡献,之后将这张贴图传递给Shader,Shader通过颜色任意一个通道的值,在mipmap的不同层次采样即可。

  声控闪电则是采集声音音量大小,以一定权重乘在最后frag返回的颜色值上,让屏幕变亮即可。

  现在TODO的有两个,一个是如何优化触摸擦除雨点相关逻辑,在手机端比较卡顿;另外是要研究一下这个雨点Shader究竟是如何用数学计算画出雨点的。

  ----------------------------------------------------------分割线----------------------------------------------------------------

  后来研究了一下shader源码,静态雨滴的能看懂,落下的雨滴数学部分不是很看的懂。

  大致原理的话,就是根据分格子,然后每个像素点根据和中心的相对坐标位置,计算每个像素点的清晰程度即可;比如静态雨滴的就是雨滴中心是比较清晰的,也就是blur程度较低的,边缘是模糊的,然后根据时间变化即可。之后有个细节每个点计算出法线以后,向着法线偏移一点,就能做出类似于雨滴折射的效果。

  这边贴一下我写了部分注释的shader代码吧。

  1 // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
  2 
  3 Shader "Shadertoy/Template" { 
  4     Properties{
  5         iMouse ("Mouse Pos", Vector) = (100, 100, 0, 0)
  6         iChannel0("iChannel0", 2D) = "white" {}  
  7         iChannelResolution0 ("iChannelResolution0", Vector) = (100, 100, 0, 0)
  8         touchTex("touchTex", 2D) = "white" {}  
  9     }
 10 
 11     CGINCLUDE    
 12     #include "UnityCG.cginc"   
 13     #pragma target 3.0      
 14 
 15     #define vec2 float2
 16     #define vec3 float3
 17     #define vec4 float4
 18     #define mat2 float2x2
 19     #define mat3 float3x3
 20     #define mat4 float4x4
 21     #define fract frac
 22     #define textureLod tex2Dlod
 23     #define iTime _Time.y
 24     #define iGlobalTime _Time.y
 25     #define mod fmod
 26     #define mix lerp
 27     #define fract frac
 28     #define texture2D tex2D
 29     #define iResolution _ScreenParams
 30     #define gl_FragCoord ((_iParam.scrPos.xy/_iParam.scrPos.w) * _ScreenParams.xy) // 前半部分是[0, 1],整体是屏幕像素的坐标
 31 
 32     #define PI2 6.28318530718
 33     #define pi 3.14159265358979
 34     #define halfpi (pi * 0.5)
 35     #define oneoverpi (1.0 / pi)
 36 
 37     fixed4 iMouse;
 38     sampler2D iChannel0;
 39     fixed4 iChannelResolution0;
 40     sampler2D touchTex;
 41     int downSample;
 42     float volumeVal;
 43 
 44     struct v2f {    
 45         float4 pos : SV_POSITION;    
 46         float4 scrPos : TEXCOORD0;   
 47     };              
 48 
 49     v2f vert(appdata_base v) {  
 50         v2f o;
 51         o.pos = UnityObjectToClipPos (v.vertex);
 52         o.scrPos = ComputeScreenPos(o.pos);
 53         return o;
 54     }  
 55 
 56     vec4 main(vec2 fragCoord);
 57 
 58     fixed4 frag(v2f _iParam) : COLOR0 { 
 59         vec2 fragCoord = gl_FragCoord;
 60         return main(gl_FragCoord);
 61     }  
 62 
 63     // shader toy start
 64 
 65     // zzy
 66     float debug_float;
 67 
 68     #define S(a, b, t) smoothstep(a, b, t)
 69     //#define CHEAP_NORMALS
 70     #define HAS_HEART
 71     #define USE_POST_PROCESSING
 72 
 73     vec3 N13(float p) {
 74         //  from DAVE HOSKINS
 75     vec3 p3 = fract(vec3(p, p, p) * vec3(.1031,.11369,.13787));
 76     p3 += dot(p3, p3.yzx + 19.19);
 77     return fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
 78     }
 79 
 80     vec4 N14(float t) {
 81         return fract(sin(t*vec4(123., 1024., 1456., 264.))*vec4(6547., 345., 8799., 1564.));
 82     }
 83     float N(float t) {
 84         return fract(sin(t*12345.564)*7658.76);
 85     }
 86 
 87     // S是smoothstep的,就会变成波峰随着b变化的一个函数
 88     // 另外,smoothstep参照这个:https://zhuanlan.zhihu.com/p/157758600
 89     float Saw(float b, float t) {
 90         return S(0., b, t)*S(1., b, t);
 91     }
 92 
 93 
 94     vec2 DropLayer2(vec2 uv, float t) {
 95         vec2 UV = uv;
 96         
 97         uv.y += t*0.75; // 所有雨滴跟着画布下落 // zzy
 98         vec2 a = vec2(6., 1.);
 99         vec2 grid = a*2.;
100         // grid = float2(1, 2); // zzy
101         vec2 id = floor(uv*grid);
102         
103         float colShift = N(id.x); 
104         uv.y += colShift; // y位置随机偏移 // zzy
105         
106         id = floor(uv*grid);
107         vec3 n = N13(id.x*35.2+id.y*2376.1);
108         vec2 st = fract(uv*grid)-vec2(.5, 0); // 把x映射到[-0.5, 0.5],然后按格子loop
109         
110         float x = n.x-.5; // random [-0.5, 0.5]
111         
112         float y = UV.y*20.;
113         float wiggle = sin(y+sin(y)); // [-1, 1]
114         x += wiggle*(.5-abs(x))*(n.z-.5);
115         x *= .7;
116         float ti = fract(t+n.z);
117         // ti = debug_float; // zzy
118         // 下面的y值控制了雨滴在y轴的长度,y越接近1,越短,超过1就没了
119         // 前半部分在0.85的时候达到最大值;整体的值域在[0, 1]之间,0.9是为了让前半部分变小,这样值变小,这样整体值域就在[0.05, 0.95],不那么接近边缘效果会更好一些
120         // 同时根据Saw函数可知,在0.85的时候y值最大,雨滴最短,也就是说,雨滴是先变短,到85%的时候最短,然后迅速变到变长
121         // 可以想象成0.85的时候是开始,雨滴迅速下坠,然后停一会,然后慢慢的收缩到最小(配合画布移动,就达到了雨滴下坠的效果)
122         y = (Saw(.85, ti)-.5)*.9+.5;
123         vec2 p = vec2(x, y);
124         
125         float d = length((st-p)*a.yx);
126         
127         // 这里可以知道,p离st越近,mainDrop越大,最后的结果越清晰
128         float mainDrop = S(.4, .0, d);
129 
130         // mainDrop = d; // zzy
131         
132         float r = sqrt(S(1., y, st.y));
133         float cd = abs(st.x-x);
134         float trail = S(.23*r, .15*r*r, cd);
135         float trailFront = S(-.02, .02, st.y-y);
136         trail *= trailFront*r*r;
137         
138         y = UV.y;
139         float trail2 = S(.2*r, .0, cd);
140         float droplets = max(0., (sin(y*(1.-y)*120.)-st.y))*trail2*trailFront*n.z;
141         y = fract(y*10.)+(st.y-.5);
142         float dd = length(st-vec2(x, y));
143         droplets = S(.3, 0., dd);
144         // r = 0; // zzy
145         float m = mainDrop+droplets*r*trailFront;
146 
147         //m += st.x>a.y*.45 || st.y>a.x*.165 ? 1.2 : 0.;
148         return vec2(m, trail);
149     }
150 
151     // 返回的值越小,越接近maxBlur,就越糊;值越大,越接近minBlur
152     float StaticDrops(vec2 uv, float t) {
153         uv *= 40.;
154         
155         // 格子化雨点
156         vec2 id = floor(uv);
157         uv = fract(uv)-.5; // [-0.5, 0.5]
158         vec3 n = N13(id.x*107.45+id.y*3543.654); // random [0, 1]
159         vec2 p = (n.xy-.5)*.7;
160         // 让每个格子的雨点位置随机产生偏差
161         float d = length(uv-p);
162         
163         // fade随着时间,从1变化到0
164         // fade越小,所有值会趋向于maxBlur,fade较大时,就是清晰的样子,
165         // 所以随时间从1变化到0,表现为blur块晕开
166         // 另外,由于加上了位置随机值 n.z,所有的起始时间被错开了
167         float fade = Saw(.025, fract(t+n.z));
168         // S(.3, 0., d),d越接近雨滴中心,越趋向于1,离雨滴越远,越接近0;
169         // 所以这里其实反过来了,会变成一个blur值低(清晰)的圆,随后乘上fade(从1到0变化),中间的返回值越来越小,越来越接近maxBlur,
170         // 于是就会看到一个清晰的圆越来越小,模拟的就是雨滴打到玻璃上,然后慢慢消失的情形
171         // fract(n.z*10.) 是个根据位置的随机值,让每个圆的半径不一样
172         float c = S(.3, 0., d)*fract(n.z*10.)*fade;
173         return c;
174     }
175 
176     vec2 Drops(vec2 uv, float t, float l0, float l1, float l2) {
177         float s = StaticDrops(uv, t)*l0; 
178         vec2 m1 = DropLayer2(uv, t)*l1;
179         vec2 m2 = DropLayer2(uv*1.85, t)*l2;
180         
181         // zzy
182         // m1 = float2(0,0);
183         // m2 = float2(0, 0);
184 
185         float c = s+m1.x+m2.x;
186         c = S(.3, 1., c);
187 
188         return vec2(c, max(m1.y*l0, m2.y*l1));
189     }
190 
191     vec4 main( /*out vec4 fragColor,*/ vec2 fragCoord )
192     {
193         vec4 fragColor;
194 
195         // 映射到[-0.5, 0.5],需要注意,不是标准的这个范围,因为分母是y不是xy
196         vec2 uv = (fragCoord.xy-.5*iResolution.xy) / iResolution.y;
197         // [0, 1]
198         vec2 UV = fragCoord.xy/iResolution.xy;
199         vec3 M = iMouse.xyz/iResolution.xyz;
200         float T = iTime+M.x*2.;
201 
202         // 采样Touch
203         vec4 touchColor = tex2D(touchTex, UV);
204 
205         // #ifdef HAS_HEART
206         // T = mod(iTime, 102.);
207         // T = mix(T, M.x*102., M.z>0.?1.:0.);
208         // #endif
209         
210         
211         float t = T*.2;
212         
213         float rainAmount = iMouse.z>0. ? M.y : sin(T*.05)*.3+.7;
214         // float rainAmount = 1;  // zzy
215 
216         float maxBlur = mix(3., 6., rainAmount);
217         float minBlur = 2.;
218         
219         float story = 0.;
220         float heart = 0.;
221         
222         // #ifdef HAS_HEART
223         // story = S(0., 70., T);
224         
225         // t = min(1., T/70.);                        // remap drop time so it goes slower when it freezes
226         // t = 1.-t;
227         // t = (1.-t*t)*70.;
228         
229         // float zoom= mix(.3, 1.2, story);        // slowly zoom out
230         // uv *=zoom;
231         // minBlur = 4.+S(.5, 1., story)*3.;        // more opaque glass towards the end
232         // maxBlur = 6.+S(.5, 1., story)*1.5;
233         
234         // vec2 hv = uv-vec2(.0, -.1);                // build heart
235         // hv.x *= .5;
236         // float s = S(110., 70., T);                // heart gets smaller and fades towards the end
237         // hv.y-=sqrt(abs(hv.x))*.5*s;
238         // heart = length(hv);
239         // heart = S(.4*s, .2*s, heart)*s;
240         // rainAmount = heart;                        // the rain is where the heart is
241         
242         // maxBlur-=heart;                            // inside the heart slighly less foggy
243         // uv *= 1.5;                                // zoom out a bit more
244         // t *= .25;
245         // #else
246         // float zoom = -cos(T*.2);
247         // uv *= .7+zoom*.3;
248         // #endif
249         // UV = (UV-.5)*(.9+zoom*.1)+.5;
250         
251         float staticDrops = S(-.5, 1., rainAmount)*2.;
252         float layer1 = S(.25, .75, rainAmount);
253         float layer2 = S(.0, .5, rainAmount);
254         
255         
256         vec2 c = Drops(uv, t, staticDrops, layer1, layer2);
257         // 下面的法线是为了采样有一些偏差,让雨滴看起来有折射一般的效果
258     #ifdef CHEAP_NORMALS
259             vec2 n = vec2(dFdx(c.x), dFdy(c.x));// cheap normals (3x cheaper, but 2 times shittier ;))
260         #else
261             vec2 e = vec2(.001, 0.);
262             float cx = Drops(uv+e, t, staticDrops, layer1, layer2).x;
263             float cy = Drops(uv+e.yx, t, staticDrops, layer1, layer2).x;
264             vec2 n = vec2(cx-c.x, cy-c.x);        // expensive normals
265         #endif
266 
267         // zzy
268         // n = 0;
269         
270         
271         // #ifdef HAS_HEART
272         // n *= 1.-S(60., 85., T);
273         // c.y *= 1.-S(80., 100., T)*.8;
274         // #endif
275         
276         // 按照雨滴c的返回值x,越小越糊;同时,y值越大,整体越清晰一些(这里不太能理解原因,貌似是竖着落下来的雨有用到,和trail有关系)
277         float focus = mix(maxBlur-c.y, minBlur, S(0.1, 0.2, c.x));
278         focus = lerp(focus, 1, touchColor.r);
279         // focus = 1; // zzy
280         // focus *= (1 - touchColor.r * 0.8);
281         vec3 col = textureLod(iChannel0, float4(UV+n, 0, focus)).rgb;
282         
283         #ifdef USE_POST_PROCESSING
284         t = (T+3.)*.5;                                        // make time sync with first lightnoing
285         float colFade = sin(t*.2)*.5+.5+story;
286         col *= mix(vec3(1., 1., 1.), vec3(.8, .9, 1.3), colFade);    // subtle color shift
287         float fade = S(0., 10., T);                            // fade in at the start
288         // float lightning = sin(t*sin(t*10.));                // lighting flicker
289         // lightning *= pow(max(0., sin(t+sin(t))), 10.);        // lightning flash
290         float lightning = volumeVal / 15;
291         col *= 1.+lightning*fade*mix(1., .1, story*story);    // composite lightning
292         col *= 1.-dot(UV-=.5, UV);                            // vignette
293                                                     
294         // #ifdef HAS_HEART
295         //     col = mix(pow(col, vec3(1.2, 1.2, 1.2)), col, heart);
296         //     fade *= S(102., 97., T);
297         // #endif
298         
299         // col *= fade;                                        // composite start and end fade
300         #endif
301         
302         // col = vec3(heart);
303         fragColor = vec4(col, 1.);
304         return fragColor;
305     }
306 
307     // shader toy end
308 
309     ENDCG    
310 
311     SubShader {    
312         ZTest Always
313         Pass {    
314             CGPROGRAM    
315 
316             #pragma vertex vert    
317             #pragma fragment frag    
318             #pragma fragmentoption ARB_precision_hint_fastest     
319 
320             ENDCG    
321         }    
322     }     
323     FallBack Off    
324 }
shader

   ----------------------------------------------------------分割线----------------------------------------------------------------

  后来在网络上找到的一个类似的教程。Unity Shader 窗前雨滴效果 - 灰信网(软件开发博客聚合) (freesion.com)