雨滴玻璃Shader实现思路


最终效果

实现思路

网格

首先使用一个Unlitshader,为了限制雨滴的移动范围,需要先定义出网格,首先将采样得到的纹理坐标乘一个数,然后取小数部分,就可以得到重复的网格:

1
2
3
4
5
6
7
8
9
10
11
12
13
fixed4 frag (v2f i) : SV_Target
{
float4 col = 0;

float2 aspect = float2(2,1);
float2 uv = i.uv * _GridSize * aspect;
float2 grid_uv = frac(uv) - 0.5;//把原点移到中间

//定义网格边界线
if(grid_uv.x > 0.48 || grid_uv.y > 0.49) col = float4(1, 0, 0, 1);

return col;
}

雨滴

使用smoothstep,通过计算到网格中心的距离来画出雨滴:

1
2
float drop = smoothstep(0.05, 0.03, length(grid_uv / aspect));
col += drop;

移动逻辑

额外定义两个变量来控制雨滴偏移的量,通过改变这个两个变量来实现雨滴的移动,直接对网格坐标做加减即可:

1
2
3
4
5
6
7
float x = 0;
float y = 0;

float2 dropPos = (grid_uv - float2(x, y)) / aspect;

float drop = smoothstep(0.05, 0.03, length(dropPos));
col += drop;

然后就是最重要的,正常情况下雨滴落在窗户上,会先缓慢下滑,然后快速下滑,先使用一个函数模拟这个过程:

为了避免雨滴出现上滑,所以再同时把网格的y往下移,就可以做出这样的视觉效果:不断有雨滴从缓慢下滑到快速下滑:

1
2
3
4
5

float2 uv = i.uv * _GridSize * aspect;
uv.y += t * 0.25;//下移网格

float y = -sin(t + sin(t + sin(t) * .5)) * 0.45;

雨滴轨迹

思路就是同样在生成drop的同时生成几个比它小的drop:

1
2
3
4
5
6
7
float2 trailPos = (grid_uv - float2(x, t * 0.25)) / aspect;
trailPos.y = (frac(trailPos.y * 8) - 0.5) / 8;//重复的轨迹个数
float trail = smoothstep(0.03, 0.01, length(trailPos));//更小的drop

//舍弃掉在drop下面的小drop
trail *= dropPos.y > 0 ? 1 : 0;
//在drop的上面,y大于0,在下面就小于0,所以在下面的直接乘0舍弃

在这个基础上再去做颜色的渐变:

1
trail *= smoothstep(0.5, y, grid_uv.y);

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

1
2
float w = i.uv.y * 10;
float x = sin(3 * w) * pow(sin(w), 6) * 0.45;

随机化

为了让每个雨滴的轨迹,下落时间不一样,还需要分离每一个格子,让每个格子都有一个独有的id号,然后就可以针对不同的格子去做随机:

1
float2 grid_id = floor(uv);

格子有了不同的颜色,即有了不同的id。然后根据这个float2的id去生成一个随机数:

1
2
3
4
5
6
7
8
float GetRandom(float2 p)
{
p = frac(p * float2(123.33, 233.233));
p += dot(p, p + 34.33);
return frac(p.x * p.y);
}

col += GetRandom(grid_id);

成功给每个格子生成了一个随机数:

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

1
2
3
4
5
6
7
8
 float gridValue = GetRandom(grid_id);
t += gridValue * 6.28;//sin函数是2PI

float w = i.uv.y * 10;
float x = (gridValue - 0.5) * 0.8;//一个-0.4 ~ 0.4的数
x += (0.4 - abs(x)) * sin(3 * w) * pow(sin(w), 6) * 0.45;//越靠近格子边缘的雨滴左右移动的幅度越小,避免雨滴滑到格子外面
float y = -sin(t + sin(t + sin(t) * .5)) * 0.45;
y -= (grid_uv.x - x) * (grid_uv.x - x);

这样就已经有了水滴随机下滑的效果。

雾气效果

1
2
3
4
float fogTrail = dropPos.y > 0 ? 1 : 0;
fogTrail *= smoothstep(0.5, y, grid_uv.y);
trail *= fogTrail;
fogTrail *= smoothstep(0.05, 0.04, abs(dropPos.x));

采样

然后就是拿这个图去对窗户后面的景色进行采样:

1
2
3
float2 offsets = drop * dropPos + trail * trailPos;

col = tex2D(_MainTex, i.uv + offsets * _Distortion);

然后就已经可以得到很棒的效果了:

为了做到模糊的效果,使用tex2Dlod进行采样:

1
2
3
4
5
float2 offsets = drop * dropPos + trail * trailPos;

float blur = _Blur * 7 * (1 - fogTrail);

col = tex2Dlod(_MainTex, float4(i.uv + offsets * _Distortion,0,blur));

雨滴层级

使用一个for循环去控制多层级的雨滴,数值越大,雨滴越多:

1
2
3
4
5
float3 drops = DrawDrop(i.uv, t);
for(int k = 1; k <= _RainLayers; k++)
{
drops += DrawDrop(i.uv * (1.13 + 0.012 * k) + 0.98 * k, t);
}

窗户效果

要真正有着透过窗户看到外面的效果,可以使用grabpass实现。为了正确得到镜子后面的效果,必须让它最后渲染,所以把它的队列为Transparent,在不透明物体渲染完之后再渲染它。计算出抓取texture的坐标后用tex2Dproj进行采样,就可以得到正确的透视效果:

1
2
3
4
5
6
7
8
9
struct v2f
{
float2 uv : TEXCOORD0;
float4 grab_uv : TEXCOORD1;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

col = tex2Dproj(_GrabTexture, i.grab_uv);

模糊效果

模糊效果的思路和做高斯模糊相似,直接在采样的时候对坐标进行偏移,然后采样后做均值就行:

1
2
3
4
5
6
7
8
9
10
11
proj_uv += drops.xy * _Distortion;//控制雨滴程度

float a = GetRandom(i.uv) * 6.283;
for(float j = 0; j < _SampleNum; j++)
{
float2 sampleOffset = float2(sin(a),cos(a)) * blur * 0.01;
float d = sqrt(frac(sin((j + 1) * 546.) * 5489.));
col += tex2D(_GrabTexture, proj_uv + sampleOffset * d);
a++;
}
col /= _SampleNum;

现在已经是挺不错的效果了。

Mipmap Level

现在有一个比较明显的问题是当距离比较远的时候,水滴看起来会很像闪烁着的点:

这是因为当相机离目标物体比较远时,目标物体只占屏幕很小一块区域(pixels),但我们还是用了正常尺寸的texture对目标物体进行渲染,此时一个pixel对应多个texel,采样就会不足。所以要基于距离的远近去做细节的程度,于是使用fwidth函数,会返回这个值在当前像素和它的下一个相邻像素之间的差值(与X和Y方向上的下一个像素上该值差的绝对值和)。

我们可以把这个差值放大,然后把它限制在0~1之间,把它作为一个系数去控制雨滴的细节程度:

1
2
3
4
5
6
float fade = 1 - saturate(fwidth(i.uv) * 50);

float blur = _Blur * 7 * (1 - drops.z * fade);

float2 proj_uv = i.grab_uv.xy / i.grab_uv.w;
proj_uv += drops.xy * _Distortion * fade;

这样就不会出现很远的距离还能看见一个一个的雨滴了:

OVER!


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC