欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity Shader 窗前雨滴效果

程序员文章站 2024-02-12 22:23:46
...

窗前雨滴效果(截图)

Unity Shader 窗前雨滴效果

实现过程

0. 栅格化

float aspectRatio = 4.0;//每一行雨滴的宽高比
float tileNum = 5;//平铺数量
uv *= fixed2(tileNum * aspectRatio,tileNum);//栅格化uv
uv = frac(uv);
uv -=0.5;

1. 绘制主要雨滴

float r = length(uv);
r = smoothstep(0.2,0.1,r);
return float2(r,0.);
uv.y *= aspectRatio;

2. 绘制尾迹

//添加尾迹
float tailTileNum = 3.0;
float2 tailUV =uv *  float2(1.0,tailTileNum);
tailUV.y = frac(tailUV.y) - 0.5;
tailUV.x *= tailTileNum;
float rtail = length(tailUV);
rtail = smoothstep(0.2,0.1,rtail);

3. 尾迹塑形

//在雨滴上面总共有
float rtail = length(tailUV);
//尾迹塑形
rtail *= uv.y;//上面的y值大 使得雨滴形状变小
rtail = smoothstep(0.2,0.1,rtail);
//切除掉大雨滴下面的部分
rtail *= smoothstep(0.2,0.3,uv.y);//0.2以下的部分雨滴太大,切掉

4. 融合大雨滴和尾迹,并给雨滴添加模拟法线

float2 allUV = float2(rtail*tailUV+r*uv);

5. 把雨滴法线用于采样背景贴图

fixed4 finalColor = tex2D(_MainTex, uv + Rain(uv)*2.);

6. 让整个雨滴动起来

fixed4 finalColor = tex2D(_MainTex, uv + Rain(uv)*2.);

7. 让雨滴滚动起来更有节奏

//这里是屏幕空间
uv.y += time * PI2 /period / tileNum *0.45* 0.55;//加点y轴移动
//other code

float period = 5;//second per circle
float t = time * PI2 /period;
//这里是格子空间
//此处uv值范围为(-0.5,0.5)
uv.y += sin(t+sin(t+sin(t)*0.55))*0.45;
uv.y *= aspectRatio;

8. 加点基于格子的随机值

fixed2 idRand = Rand22(floor(uv));
t += idRand.x * PI2;//添加Y随机值
/////
uv.x += (idRand.x-.5)*.6;//添加x轴随机偏移

9. 添加斜率的变化,模拟风的效果

flaot DEG2RAD = 3.14159 /180;
flaot ratoteDeg = 20.0 * DEG2RAD;
float s = sin(ratoteDeg);
float c = cos(ratoteDeg);
float2x2 rot = float2x2(c, -s, s, c);
uv = mul(rot,uv);

10.多加几层不同大小的雨滴

rainUV += Rains(uv,152.12,moveSpd);
rainUV += Rains(uv*2.32, 25.23, moveSpd);

C#源码

using UnityEngine;

public class ScreenRain : MonoBehaviour
{
    [Range(0,1)]
    public float blend = 1;

    [Range(0, 1)]
    public float moreRainAmount = 1;

    public bool wipe = true;

    [Range(0.1f, 20f)]
    public float wipeSizeX = 0.8f;

    [Range(0.1f, 20f)]
    public float wipeSizeY = 8.5f;

    public bool debugWipe = false;

	private Material mtrl = null;

    private int srcTexPropId = 0; 
    private int blendPropId = 0;
    private int wipeRTPropId = 0;
    private int wipeRTCanvasPropId = 0;
    private int wipeRTCanvas2PropId = 0;
    private int wipeScreenPosPropId = 0;
    private int wipeScreenSizePropId = 0;
    private int wipeSizePropId = 0;
    private int moreRainAmountPropId = 0;

    private RenderTexture wipeRT = null;
    private RenderTexture wipeRTCanvas = null;
    private RenderTexture wipeRTCanvas2 = null;
    private Vector2 wipeScreenPos;

    private void Awake()
    {
        mtrl = new Material(Shader.Find("Hidden/ScreenRain"));

        srcTexPropId = Shader.PropertyToID("_SrcTex");
        blendPropId = Shader.PropertyToID("_Blend");
        wipeRTPropId = Shader.PropertyToID("_WipeTex");
        wipeRTCanvasPropId = Shader.PropertyToID("_WipeCanvasTex");
        wipeRTCanvas2PropId = Shader.PropertyToID("_WipeCanvas2Tex");
        wipeScreenPosPropId = Shader.PropertyToID("_WipeScreenPos");
        wipeScreenSizePropId = Shader.PropertyToID("_WipeScreenSize");
        wipeSizePropId = Shader.PropertyToID("_WipeSize");
        moreRainAmountPropId = Shader.PropertyToID("_MoreRainAmount");

        wipeScreenPos = Vector2.one * -9000;
    }

    private void Update()
    {
        if(wipe)
        {
            if(Input.GetMouseButton(0))
            {
                //if (camera.gameObject.Instance.SetCamRainState == 0) {
                    //return;
                //}
                wipeScreenPos = Input.mousePosition;
            }
            else
            {
                wipeScreenPos = Vector2.one * -9000;
            }
        }
    }

	private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if(mtrl == null || mtrl.shader == null || !mtrl.shader.isSupported)
        {
            enabled = false;
            return;
        }

        if(wipe)
        {
            if(wipeRT == null || !wipeRT.IsCreated())
            {
                DestroyWipeRT();
                wipeRT = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.Default);
                Graphics.Blit(Texture2D.blackTexture, wipeRT);
            }
            if(wipeRTCanvas == null || !wipeRTCanvas.IsCreated())
            {
                DestroyWipeRTCanvas();
                wipeRTCanvas = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.Default);
                wipeRTCanvas2 = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.Default);
                Graphics.Blit(Texture2D.blackTexture, wipeRTCanvas);
            }
            mtrl.SetTexture(wipeRTPropId, wipeRT);
            mtrl.SetTexture(wipeRTCanvasPropId, wipeRTCanvas);
            mtrl.SetTexture(wipeRTCanvas2PropId, wipeRTCanvas2);
            mtrl.SetVector(wipeScreenPosPropId, wipeScreenPos);
            mtrl.SetVector(wipeScreenSizePropId, new Vector4(Screen.width, Screen.height, 0, 0));
            mtrl.SetVector(wipeSizePropId, new Vector4(wipeSizeX, wipeSizeY, 0, 0));
            Graphics.Blit(null, wipeRT, mtrl, 2);
            Graphics.Blit(null, wipeRTCanvas, mtrl, 3);
            Graphics.Blit(null, wipeRTCanvas2, mtrl, 4);
            Graphics.Blit(wipeRTCanvas2, wipeRTCanvas);
            mtrl.EnableKeyword("WIPE");
        }
        else
        {
            DestroyWipeRT();
            DestroyWipeRTCanvas();
            mtrl.DisableKeyword("WIPE");
        }

        mtrl.SetFloat(moreRainAmountPropId, moreRainAmount);
        mtrl.SetTexture(srcTexPropId, src);
        mtrl.SetFloat(blendPropId, blend);
        int rtSizeScale = 1;
#if UNITY_EDITOR
        rtSizeScale = 2;
#else
        rtSizeScale = 3; // 性能更好
#endif
        RenderTexture srcRT = RenderTexture.GetTemporary(src.width / rtSizeScale, src.height / rtSizeScale, 0, src.format);
        RenderTexture destRT = RenderTexture.GetTemporary(srcRT.width, srcRT.height, 0, srcRT.format);
        Graphics.Blit(src, srcRT);
        Graphics.Blit(srcRT, destRT, mtrl, 0);
        Graphics.Blit(destRT, dest, mtrl, 1);
        RenderTexture.ReleaseTemporary(srcRT);
        RenderTexture.ReleaseTemporary(destRT);
    }

#if UNITY_EDITOR
    // Debug
    private void OnGUI()
    {
        if(debugWipe)
        {
            if(wipeRT != null)
            {
                GUI.DrawTexture(new Rect(0,0,wipeRT.width / 4, wipeRT.height/4), wipeRT, ScaleMode.ScaleAndCrop, false);
            }
            if(wipeRTCanvas != null)
            {
                GUI.DrawTexture(new Rect(0,wipeRTCanvas.height/4,wipeRTCanvas.width / 4, wipeRTCanvas.height/4), wipeRTCanvas, ScaleMode.ScaleAndCrop, false);
            }
        }
    }
#endif

    private void OnDestroy()
    {
        if(mtrl != null)
        {
            DestroyImmediate(mtrl);
            mtrl = null;
        }

        DestroyWipeRT();
        DestroyWipeRTCanvas();
    }

    private void OnDisable()
    {
        DestroyWipeRT();
        DestroyWipeRTCanvas();
    }

    private void DestroyWipeRT()
    {
        if(wipeRT != null)
        {
            Destroy(wipeRT);
            wipeRT = null;
        }
    }

    private void DestroyWipeRTCanvas()
    {
        if(wipeRTCanvas != null)
        {
            Destroy(wipeRTCanvas);
            wipeRTCanvas = null;
        }
        if(wipeRTCanvas2 != null)
        {
            Destroy(wipeRTCanvas2);
            wipeRTCanvas2 = null;
        }
    }
}

Shader源码

Shader "Hidden/ScreenRain"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 3.0
			#pragma multi_compile _ WIPE
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float4 scrPos : TEXCOORD0;
			};

			sampler2D _MainTex;
			sampler2D _SrcTex;
			sampler2D _WipeCanvasTex;

			uniform half _MoreRainAmount;

			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));
			}
			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 smoothstep(0., b, t)*smoothstep(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 = smoothstep(.4, .0, d);
				
				float r = sqrt(smoothstep(1., y, st.y));
				float cd = abs(st.x-x);
				float trail = smoothstep(.23*r, .15*r*r, cd);
				float trailFront = smoothstep(-.02, .02, st.y-y);
				trail *= trailFront*r*r;
				
				y = UV.y;
				float trail2 = smoothstep(.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 = smoothstep(.3, 0., dd);
				float m = mainDrop+droplets*r*trailFront; 
				
				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 = smoothstep(.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 = smoothstep(.3, 1., c);
				
				return float2(c, max(m1.y*l0, m2.y*l1));
			}

			float2 DropsDynamic(float2 uv, float t, float l1, float l2)
			{
				float2 m1 = DropLayer2(uv, t)*l1;
				float2 m2 = DropLayer2(uv*1.75, t)*l2;
				
				float c = m1.x+m2.x;
				c = smoothstep(.4, 1., c);
				
				return float2(c, max(0, m2.y*l1));
			}

			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.scrPos = ComputeScreenPos(o.pos);
				return o;
			}

			fixed4 frag (v2f _iParam) : SV_Target
			{
				float2 fragCoord = (_iParam.scrPos.xy / _iParam.scrPos.w) * _ScreenParams.xy;
				float4 fragColor = 0;
        		
				float2 uv = (fragCoord.xy - .5*_ScreenParams.xy) / _ScreenParams.y;
				float2 UV = fragCoord.xy / _ScreenParams.xy;
				float3 M = 2;
				float T = _Time.y + M.x*2.;

				float t = T*(.2+0.1*_MoreRainAmount);

				float rainAmount = M.y;

				uv *= 0.5;

				float staticDrops = smoothstep(-.5, 1., rainAmount)*2.;
				float layer1 = smoothstep(.25, .75, rainAmount);
				float layer2 = smoothstep(.0, .5, rainAmount);

				float2 n = float2(0, 0);
				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;
				n += float2(cx - c.x, cy - c.x);
				float moreRainAmount = 1.25 + 1.25 * _MoreRainAmount;
				for(float i = 1.25; i < moreRainAmount; i+=0.25)
				{
					float2 _c = DropsDynamic(uv, t*i, layer1, layer2);
					float _cx = DropsDynamic(uv + e, t*i, layer1, layer2).x;
					float _cy = DropsDynamic(uv + e.yx, t*i, layer1, layer2).x;
					n += float2(_cx - _c.x, _cy - _c.x);
				}

				float blend = (n.x + n.y)*(1.75 + _MoreRainAmount);

#if defined(WIPE)
				float wipe = tex2D(_WipeCanvasTex, UV).r;
				wipe = saturate(pow(wipe, 0.2));
				float3 col = tex2D(_MainTex, UV + n * (1 - wipe)).rgb;
#else
				float3 col = tex2D(_MainTex, UV + n).rgb;
#endif

				fragColor = float4(col, blend);

				return fragColor;
			}
			ENDCG
		}

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			sampler2D _SrcTex;
			sampler2D _MainTex;
			half _Blend;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 rainC = tex2D(_MainTex, i.uv);
				fixed4 mainC = tex2D(_SrcTex, i.uv);
				fixed blend = rainC.a * _Blend;
				mainC.rgb = rainC.rgb * blend + mainC.rgb * (1-blend);
				return mainC;
			}
			ENDCG
		}

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			float4 _WipeScreenPos;
			float4 _WipeScreenSize;
			float4 _WipeSize;

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				float2 currScreenPos = i.uv * _WipeScreenSize;
				float4 wipeSize = float4(100,100,0,0) * _WipeSize.xyzw;
				wipeSize.xy = float2(1,1) / float2(dot(wipeSize.xz,wipeSize.xz), dot(wipeSize.yw,wipeSize.yw));
				float4 radius = float4(_WipeScreenPos.xy - currScreenPos.xy, 0, 0);
				radius.xy = float2(dot(radius.xz,radius.xz), dot(radius.yw,radius.yw));
				float inside = 1 - min(dot(radius.xy, wipeSize.xy), 1);
				return inside;
			}
			ENDCG
		}

		Pass
		{
			Blend One One

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			sampler2D _WipeTex;

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				return tex2D(_WipeTex, i.uv);
			}
			ENDCG
		}

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			sampler2D _WipeCanvasTex;

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			float4 frag (v2f i) : SV_Target
			{
				float4 c = tex2D(_WipeCanvasTex, i.uv);
				return c - 0.005f;
			}
			ENDCG
		}
	}
}