转https://zhuanlan.zhihu.com/p/60884288 黄小明
这是一个老外制作的一个水滴在玻璃上流淌的shader,效果非常好
所以偷偷学了过来,因为内容有点长我分为两个部分
我会尽可能详细解释制作思路和部分函数
(都是为了你,开心吗?)
-------------------------
shader效果
首先我们先仔细看下效果,要大概有一个基础的思路,不去考虑细节,最简单的基础的去解释下这幅画面,是不是雨滴在做一个向下的运动
思路图
那么接下来开始我们从第一步,做一个水滴,让他在UV上运动,然后我们再一步步加效果
-------------------------
首先我们来做一些准备工作
一双勤劳的手
一张图片
一颗刚刚被甩的心(这个很重要,心境没到位,怎么能做的好?)
不BB了,我们开始,准备工作自己想办法!!!!!
来我们先打开Unity新建一个UnlitShader,把里面关于FOG的先删除掉,我们先不考虑FOG
首先我们需要使用到两套UV,然后gv是我们用来画水滴的,我们想要更窄一些
fixed4 frag (v2f i) : SV_Target
{
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
float2 gv = frac(uv)-0.5;
col.rg = gv;
return col;
}

Frac函数的定义——可以简单理解为去除整数只保留小数

目前得到的结果
接下来我们要在每个格子中间画圆(水滴),做到这一步就需要用到length函数,因为我们需要控制大小所以我们还要用到smoothstep函数,然后我们注意一点,因为我们在前面给uv乘了aspect,所以我们需要在gv除以aspect这样我们才能得到一个正圆而不是一个椭圆。
然后我们为了方便查看,我们加上一段,对比gv的x和y返回一个(1,0,0,0)也就是红色
这个很好理解我们之前给gv-0.5,所以现在gv是(-0.5.0.5)最大的值只有0.5。
fixed4 frag (v2f i) : SV_Target
{
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
float2 gv = frac(uv)-0.5;
// 注意给gv除以aspect,否则会得到一个椭圆
float drop = smoothstep(0.05,0.03,length(gv/aspect));
col += drop;
// col.rg = gv;
// 为了观察方便制作线条
if (gv.x > 0.48 || gv.y > 0.49) {
return float4 (1, 0, 0, 0);
}
return col;
}

目前得到的结果
我们现在就需要让这个点动起来了,首先我们需要用到Time这个是Unity给的,我们直接用就好了,然后我们用sin函数做循环,但是单纯的sin满足不了我们的要求,我们需要一个快速下落缓慢回来的动画,所以我们可以去找一条满足的条件的函数曲线(推荐网站:
https://www.desmos.com/calculator
非常好用,自动生成曲线,只需要输入数值,不懂就瞎乘嘛只要能得到想要曲线就好)

-sin(x+sin(x+sin(x)0.5))
fixed4 frag (v2f i) : SV_Target
{
float t = _Time.y;
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
float2 gv = frac(uv)-0.5;
float x = 0;
float y =-sin(t+sin(t+sin(t)*0.5))*0.45;
// 控制gv.y来改变圆的位置
float2 dropPos =(gv - float2(x, y))/aspect;
float drop = smoothstep(0.05,0.03,length(dropPos));
col += drop;
// col.rg = gv;
// 为了观察方便制作线条方便理解
if (gv.x > 0.48 || gv.y > 0.49) {
return float4 (1, 0, 0, 0);
}
return col;
}

目前得到的动画
现在我们已经有了一个水滴,我们添加一个拖尾(复制drop就可以了)
fixed4 frag (v2f i) : SV_Target
{
float t = _Time.y;
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
float2 gv = frac(uv)-0.5;
float x = 0;
float y =-sin(t+sin(t+sin(t)*0.5))*0.45;
// 控制gv.y来改变圆的位置
float2 dropPos =(gv - float2(x, y))/aspect;
float drop = smoothstep(0.05,0.03,length(dropPos));
float2 dropTrailPos = (gv - float2(x, 0)) / aspect;
// 这里我们希望生成多个水珠拖尾所以我们把dropTrailPos.y乘8然后frac
// 还是一样因为之前给dropTrailPos.y乘于了8所以我们需要除以8
// 然后-0.03是因为我们得到的是一个半圆
dropTrailPos.y = (frac(dropTrailPos.y * 8)/8)-0.03;
float dropTrail = smoothstep(0.03, 0.02, length(dropTrailPos));
col += drop;
col += dropTrail;
// 为了观察方便制作线条方便理解
if (gv.x > 0.48 || gv.y > 0.49) {
return float4 (1, 0, 0, 0);
}
return col;
}

目前得到的结果
现在我们需要做一个水珠的渐变,然后让uv也动起来,这样就可以做到循环运动
fixed4 frag (v2f i) : SV_Target
{
float t = _Time.y;
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
// 控制uv运动配合水滴下落
uv.y += t * 0.25;
float2 gv = frac(uv)-0.5;
float x = 0;
float y =-sin(t+sin(t+sin(t)*0.5))*0.45;
// 控制gv.y来改变圆的位置
float2 dropPos =(gv - float2(x, y))/aspect;
float drop = smoothstep(0.05,0.03,length(dropPos));
float2 dropTrailPos = (gv - float2(x, t * 0.25)) / aspect;
// 这里我们为了生成多个水珠拖尾所以我们把dropTrailPos.y 乘8然后frac
// 还是一样因为之前给dropTrailPos.y 乘于了8所以我们需要除以8
// 然后-0.03是因为我们得到的是一个半圆
dropTrailPos.y = (frac(dropTrailPos.y * 8)/8)-0.03;
float dropTrail = smoothstep(0.03, 0.02, length(dropTrailPos));
// y值在-0.45与0.45之间,然后我们的gv.v在0.5,dropPos.y=gv.y-y,建议输出结果查看下就明白了
dropTrail *= smoothstep(-0.05, 0.05, dropPos.y);
dropTrail *= smoothstep(0.5, y, gv.y);
col += drop;
col += dropTrail;
// 为了观察方便制作线条方便理解
if (gv.x > 0.48 || gv.y > 0.49) {
return float4 (1, 0, 0, 0);
}
return col;
}

之前我们用sin控制了水滴的y方向运动,现在我们现在给水滴加上x的运动
x方向的运动尽可能要多一些波动,更像是水滴的运动

sin(3x)sin(x)^6
fixed4 frag (v2f i) : SV_Target
{
float t = _Time.y;
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
// 控制uv运动配合水滴下落
uv.y += t * 0.25;
float2 gv = frac(uv)-0.5;
// 用这个w值来控制拉伸程度
float w = i.uv.y *10;
// 现在我们不能用t来控制了,因为我们需要雨滴进行拉伸,所以我们利用uv来控制
float x = sin(3*w)*pow(sin(w),6)*0.45;
float y =-sin(t+sin(t+sin(t)*0.5))*0.45;
// 改变了水滴的形状,目前是gv.x(-0.5,0.5)相乘得到一个对称的值
y -= (gv.x-x)*(gv.x-x);
// 控制gv.y来改变圆的位置
float2 dropPos =(gv - float2(x, y))/aspect;
float drop = smoothstep(0.05,0.03,length(dropPos));
float2 dropTrailPos = (gv - float2(x,t * 0.25)) / aspect;
// 这里我们为了生成多个水珠拖尾所以我们把dropTrailPos.y乘8然后frac
// 还是一样因为之前给dropTrailPos.y乘于了8所以我们需要除以8
// 然后-0.03是因为我们得到的是一个半圆
dropTrailPos.y = (frac(dropTrailPos.y * 8)/8)-0.03;
float dropTrail = smoothstep(0.03, 0.02, length(dropTrailPos));
// y值在-0.45与0.45之间,然后我们的gv.v在0.5,dropPos.y=gv.y-y,建议输出结果查看下就明白了
dropTrail *= smoothstep(-0.05, 0.05, dropPos.y);
dropTrail *= smoothstep(0.5, y, gv.y);
col += drop;
col += dropTrail;
// 为了观察方便制作线条方便理解
if (gv.x > 0.48 || gv.y > 0.49) {
return float4 (1, 0, 0, 0);
}
return col;
}

现在我们加入noise,让雨滴随机运动。
//noise函数块
float N21(float2 p){
p = frac(p*float2(123.34,345.45));
p +=dot( p,p+34.345 ) ;
return frac(p.x*p.y);
}
fixed4 frag (v2f i) : SV_Target
{
float t = _Time.y;
float4 col = 0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
// 控制uv运动配合水滴下落
uv.y += t * 0.25;
float2 gv = frac(uv)-0.5;
// id用来得到每格水滴
float2 id = floor(uv);
// 得到每一格0或者1的随机值
float n =N21(id);
// 用t+上现在每格水滴的系数,达到每格的时间都不一样
t+= n*5.345;
// 用这个w值来控制拉伸程度
float w = i.uv.y *10;
// 我们把x也做一个随机的处理,使用n,得到-0.4,0.4的值
float x = (n-0.5)*0.8;
// 0.4-abs( x )的值是控制幅度,然后乘以之前我们做的运动曲线
x += (0.4-abs(x))*sin(3*w)*pow(sin(w),6)*0.45;
float y =-sin(t+sin(t+sin(t)*0.5))*0.45;
// 改变了水滴的形状,目前是gv.x(-0.5,0.5)相乘得到一个对称的值
y -= (gv.x-x)*(gv.x-x);
float2 dropPos =(gv - float2(x, y))/aspect;
float drop = smoothstep(0.05,0.03,length(dropPos));
float2 dropTrailPos = (gv - float2(x, t*0.25)) / aspect;
dropTrailPos.y = (frac(dropTrailPos.y * 8)/8)-0.03;
float dropTrail = smoothstep(0.03, 0.02, length(dropTrailPos));
dropTrail *= smoothstep(-0.05, 0.05, dropPos.y);
dropTrail *= smoothstep(0.5, y, gv.y);
col += drop;
col += dropTrail;
// 为了观察方便制作线条方便理解
if (gv.x > 0.48 || gv.y > 0.49) {
return float4 (1, 0, 0, 0);
}
return col;
}

现在我们可以做最后一步了,在现在的效果下,添加一个直线的拖尾,然后加上图片,用目前的drop+dropTrail改变图片的UV就可以达到水滴折射的效果了

实在没办法,我只能拿我老婆的照片来用
Shader "Billy/RainGlass_OP"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Size("Size",Float)= 1
_Distortion("Distortion",range(0,1))=1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Size;
float _Distortion;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
float N21(float2 p){
p = frac(p*float2(123.34,345.45));
p +=dot( p,p+34.345 ) ;
return frac(p.x*p.y);
}
fixed4 frag (v2f i) : SV_Target
{
float t = fmod(_Time.y,7200);
float4 col =0;
float2 aspect = float2(2, 1);
float2 uv = i.uv*_Size*aspect;
uv.y += t * 0.25;
float2 gv = frac(uv)-0.5;
float2 id = floor(uv);
float n =N21(id);
t+= n*6.28631;
float w = i.uv.y *10;
float x = (n-0.5)*0.8;
x += (0.4-abs(x))*sin(3*w)*pow(sin(w),6)*0.45;
float y =-sin(t+sin(t+sin(t)*0.5))*0.45;
y -= (gv.x-x)*(gv.x-x);
float2 dropPos =(gv - float2(x, y))/aspect;
float drop = smoothstep(0.05,0.03,length(dropPos));
float2 dropTrailPos = (gv - float2(x, t*0.25)) / aspect;
dropTrailPos.y = (frac(dropTrailPos.y* 8)/8)-0.03;
float dropTrail = smoothstep(0.03, 0.02, length(dropTrailPos));
float fogTrail = smoothstep(-0.05, 0.05, dropPos.y);
fogTrail *= smoothstep(0.5, y, gv.y);
dropTrail *= fogTrail;
fogTrail *= smoothstep(0.05,0.04,abs(dropPos.x));
col += fogTrail*0.5;
col += dropTrail;
col += drop;
float2 offs = drop*dropPos+dropTrail*dropTrailPos;
col = tex2D(_MainTex,i.uv+offs*_Distortion);
return col;
}
ENDCG
}
}
}
其实写完,这一部分先写到这,其实写完发现可能用shaderforge解释会更加方便和直观,下一部分会,贴上shaderforge的制作流程,会添加多层水滴和模糊镜面效果,如果有没看懂的留意下部分,用shaderforge试试就懂了。
记住保持一颗失恋的心,你会做的更好。
加油,骚年!
码字真不容易........