Unity3D Shader系列之UI流光效果


1. 引言

周末用两种方式实现了UI流光的效果。
第一种是使用流光贴图+遮罩(Mask)贴图的方式。
mask贴图实现流光
第二种是不需要任何贴图,纯用代码实现的流光效果。
纯计算实现方式
第一种方式的优点在于可以用遮罩贴图控制流光显示的区域,以及用贴图控制流光任意的形状(比如上面的效果就是我自己随便画的一个弯曲的形状)。
第二种方式的优点是不需要多次对贴图采样(但是会加大计算量,和第一种方式比起来哪种效率更高还真不好说,我也不知道怎么去评测)。
流光的基本原理就是uv流动。下面看看两种方式的实现。

2. 流光纹理+遮罩纹理

流光纹理和遮罩纹理一般都是一张黑白图,比如Demo中用到的两个纹理分别如下。
流光纹理:
流光纹理
遮罩纹理:
遮罩纹理
流光纹理用于控制流光的形状(白色区域),遮罩区域用于控制流光显示的区域(白色区域)。
核心代码就下面几句,一看就明白,不多说了。

fixed2 uv = IN.texcoord.xy;
uv.x -= _FlowSpeed * _Time.y;
fixed4 flow = _FlowColor * tex2D(_FlowTex, uv) * tex2D(_FlowMask, IN.texcoord).r;
color += flow;

完整shader

Shader "Custom/UI/Flow"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

		_FlowTex("Flow Tex", 2D) = "white" {}
		_FlowMask("Flow Mask", 2D) = "white" {}
		_FlowColor("Flow Color", Color) = (1, 1, 1, 1)
		_FlowSpeed("Flow Speed", Range(0, 3)) = 1

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;

			sampler2D _FlowTex;
			float4 _FlowTex_ST;
			sampler2D _FlowMask;
			fixed4 _FlowColor;
			half _FlowSpeed;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                OUT.worldPosition = v.vertex;
                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

                OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                #ifdef UNITY_UI_CLIP_RECT
                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

				fixed2 uv = IN.texcoord.xy;
				uv.x -= _FlowSpeed * _Time.y;
				fixed4 flow = _FlowColor * tex2D(_FlowTex, uv) * tex2D(_FlowMask, IN.texcoord).r;
				color += flow;

                return color;
            }
        ENDCG
        }
    }
}

3. 纯计算方式

这里参考了风宇冲的实现方式。
但是他的代码有点问题,当looptime调节过大时,会导致流光不能完整流动。

tmpBrightness = inFlash(75,i.uv,0.25f,5f,2f,0.15,3f);

风宇冲效果
所以我改了一下,核心代码如下,能够精确控制流光流动的时间。

// 角度, uv, 流光宽度(0~1), 两次流光开始的间隔时间, 流光流动流动一个完整图片的时间
fixed inFlow(float angle, float2 uv, fixed width, int interval, float duration)
{
	float rad = angle * 0.0174444;
	float tanRad = tan(rad);

	float maxYProj2X = 1.0 / tanRad;
	float totalMovX = 1 + width + maxYProj2X;

	float totalTime = interval + duration;
	int cnt = _Time.y / totalTime;
	float currentTime = _Time.y - cnt * totalTime;

	fixed flow = 0;
	if(currentTime < duration)
	{
		fixed x0 = currentTime / (duration / totalMovX);
		float yProj2X = uv.y / tanRad;
		float xLeft = x0 - width - yProj2X;
		float xRight = xLeft + width;
		float xMid = 0.5 * (xLeft + xRight);
		flow = step(xLeft, uv.x) * step(uv.x, xRight);
		// 插值,根据与中心的距离的比例来计算亮度
		flow *= (width - 2 * abs(uv.x - xMid)) / width;
	}
	return flow;
}

代码解释:
求解图示
如图,绿色部分为我们要增加流光的图片。最左侧的平行四边形为流光的起始位置,最右侧的平行四边形为流光的结束位置。
一次完整的流光过程为,从起始位置到结束位置(即从O点到E点)。
duration为从O点到E点的时间,单位为秒。
internal = duration + 下次流光开始的间隔时间。
其他地方就没什么可说的了,图示+代码应该就明白了。
完整代码

Shader "Custom/UI/Flow2"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

		_Angle ("Angle", Range(1, 89)) = 75					// 倾斜角度
		_Width ("Width", Range(0.1, 1)) = 0.25				// 流光宽度
		_Interval ("Interval", Int) = 3						// 间隔
		_Duration ("duration", Float) = 1.5					// 持续时间
		_FlowColor("Flow Color", Color) = (1, 1, 1, 1)		// 流光颜色

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;

			float _Angle;
			fixed _Width;
			int _Interval;
			float _Duration;
			fixed4 _FlowColor;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                OUT.worldPosition = v.vertex;
                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

                OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

                OUT.color = v.color * _Color;
                return OUT;
            }

			// 风宇冲的实现: http://blog.sina.com.cn/s/blog_471132920101d8zf.html
			//必须放在使用其的 frag函数之前,否则无法识别。
            //核心:计算函数,角度,uv,光带的x长度,间隔,开始时间,偏移,单次循环时间
            float inFlash(float angle,float2 uv,float xLength,int interval,int beginTime, float offX, float loopTime )
            {
                //亮度值
                float brightness =0;
               
                //倾斜角
                float angleInRad = 0.0174444 * angle;
               
                //当前时间
                float currentTime = _Time.y;
           
                //获取本次光照的起始时间
                int currentTimeInt = _Time.y / interval;
                currentTimeInt *= interval;
               
                //获取本次光照的流逝时间 = 当前时间 - 起始时间
                float currentTimePassed = currentTime -currentTimeInt;
                if(currentTimePassed > beginTime)
                {
                    //底部左边界和右边界
                    float xBottomLeftBound;
                    float xBottomRightBound;

                    //此点边界
                    float xPointLeftBound;
                    float xPointRightBound;
                   
                    float x0 = currentTimePassed-beginTime;
                    x0 /= loopTime;
           
                    //设置右边界
                    xBottomRightBound = x0;
                   
                    //设置左边界
                    xBottomLeftBound = x0 - xLength;
                   
                    //投影至x的长度 = y/ tan(angle)
                    float xProjL;
                    xProjL= (uv.y)/tan(angleInRad);

                    //此点的左边界 = 底部左边界 - 投影至x的长度
                    xPointLeftBound = xBottomLeftBound - xProjL;
                    //此点的右边界 = 底部右边界 - 投影至x的长度
                    xPointRightBound = xBottomRightBound - xProjL;
                   
                    //边界加上一个偏移
                    xPointLeftBound += offX;
                    xPointRightBound += offX;
                   
                    //如果该点在区域内
                    if(uv.x > xPointLeftBound && uv.x < xPointRightBound)
                    {
                        //得到发光区域的中心点
                        float midness = (xPointLeftBound + xPointRightBound)/2;
                       
                        //趋近中心点的程度,0表示位于边缘,1表示位于中心点
                        float rate= (xLength -2*abs(uv.x - midness))/ (xLength);
                        brightness = rate;
                    }
                }
                brightness= max(brightness,0);
               
                //返回颜色 = 纯白色 * 亮度
                float4 col = float4(1,1,1,1) *brightness;
                return brightness;
            }

			// 角度, uv, 流光宽度(0~1), 两次流光开始的间隔时间, 流光流动流动一个完整图片的时间
			fixed inFlow(float angle, float2 uv, fixed width, int interval, float duration)
			{
				float rad = angle * 0.0174444;
				float tanRad = tan(rad);

				float maxYProj2X = 1.0 / tanRad;
				float totalMovX = 1 + width + maxYProj2X;

				float totalTime = interval + duration;
				int cnt = _Time.y / totalTime;
				float currentTime = _Time.y - cnt * totalTime;

				fixed flow = 0;
				if(currentTime < duration)
				{
					fixed x0 = currentTime / (duration / totalMovX);
					float yProj2X = uv.y / tanRad;
					float xLeft = x0 - width - yProj2X;
					float xRight = xLeft + width;
					float xMid = 0.5 * (xLeft + xRight);
					flow = step(xLeft, uv.x) * step(uv.x, xRight);
					// 插值,根据与中心的距离的比例来计算亮度
					flow *= (width - 2 * abs(uv.x - xMid)) / width;
				}
				return flow;
			}

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                #ifdef UNITY_UI_CLIP_RECT
                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

				 //传进i.uv等参数,得到亮度值
                //float flow = inFlash(30, IN.texcoord, 0.5, 5/*interval*/, 2/*beginTime*/, 0/*xOffset*/, 2/*loopTime*/);
           
				fixed flow = inFlow(_Angle, IN.texcoord, _Width, _Interval, _Duration);
                color += _FlowColor * flow * step(0.5, color.a);

                return color;
            }
        ENDCG
        }
    }
}

博主个人博客本文链接。
项目链接:
链接:https://pan.baidu.com/s/1uVnAnEyQoZH8QURbxSnTfw
提取码:9zin
(备注:项目部分素材来源与unity shader 流光(1))

4. 参考文章

  • 12
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值