当前位置:   article > 正文

Unity3D Shader系列之UI流光效果_unity ui流光

unity 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;
  • 1
  • 2
  • 3
  • 4

完整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
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131

3. 纯计算方式

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

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

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

// 角度, 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

代码解释:
求解图示
如图,绿色部分为我们要增加流光的图片。最左侧的平行四边形为流光的起始位置,最右侧的平行四边形为流光的结束位置。
一次完整的流光过程为,从起始位置到结束位置(即从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
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231

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

4. 参考文章

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/109478
推荐阅读
相关标签
  

闽ICP备14008679号