UI Wooble Shader - Unity Shader

May 27, 2018
5m
Unity 3DShaders

A shader that will a add a bit of liveliness to your UI is always a good thing, especially when you are making a mobile game where grabbing and holding user attention is top priority.

Jiggly Wiggly UI

This effect is known to hypnotize users into spending big bucks in your game (sarcasm). Anyway this shader is based on the optimized UI shader provided by Unity.
Masking, Clipping and some other fancy stuff is not available when using this shader on UI components.
Let's get to making out jiggly wiggly UI shader.
We will look at the properties being declared :


Properties
{
     [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
     _Color("Tint", Color) = (1,1,1,1)
     _WobblesPerSecond("Wobbles per Second ", Float) = 2
     _WobblePeriod("Wobble Period (pixels)", Float) = 45
     _WobbleAmplitude("Wobble Amplitude (pixels)", Float) = 3.2
}

Here are the properties defined in the CG PROGRAM along with #defines:


sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;

/*1*/float _WobblesPerSecond;
/*2*/float _WobblePeriod;
/*3*/float _WobbleAmplitude;
/*4*/#define TWO_PI 6.28318
}

The things added on top of the optimized UI shader are the three floats - (1), (2) & (3) and we also define the value of 2x PI (4) as we will be using it in the vertex shader.
Now take a look the vertex shader as it does all the work:

C#

1v2f vert(appdata_t IN)
2{
3     v2f OUT;
4     /*1*/float4 worldPos = IN.vertex;
5     /*2*/float offsetInput = (_Time.y + frac(worldPos.x)) * _WobblesPerSecond * TWO_PI + worldPos.x / _WobblePeriod;
6     /*3*/worldPos.y += sin(offsetInput) * _WobbleAmplitude;
7     /*4*/worldPos.x += cos(offsetInput) * _WobbleAmplitude;
8     /*5*/OUT.vertex = UnityObjectToClipPos(worldPos);
9     OUT.texcoord = IN.texcoord;
10     #ifdef UNITY_HALF_TEXEL_OFFSET
11     OUT.vertex.xy += (_ScreenParams.zw - 1.0)*float2(-1,1);
12     #endif
13     OUT.color = IN.color * _Color;
14     return OUT;
15}

Let's breakdown the steps:

  1. We are storing world space position of vertex in worldPos.
  2. Calculate the offset that will be applied on the vertex position. 
  3. frac(worldPos.x) acts as random seed value so that each objects wiggles differently and addition with _Time.y will keep changing the offset value every frame.
  4. Then the _WobblesPerSecond multiplied with TWO_PI increases the frequency of the wiggle effect. + worldPos.x / _WobblePeriod adds additional variance to the wiggle movement, A larger WobblePeroid means a larger distance between a crest and a trough and vise-versa.
  5. Changing the y position of vertex with sin(offsetInput) which gives -1 to 1 result and then multiplying with _WobbleAmplitude to get results in -_WobbleAmplitude to +_WobbleAmplitude.
  6. Same thing as (3) except it's for changing x position of vertex and cosine is used.
  7. Converting the vertices from world space to clip space which gives the output for the vertex shader.

The fragment shader does not undergo any changes. The default values present in the UI shader is enough.

Here is the entire shader:

C#

1Shader "BitshiftProductions/Fast-UI-Jiggle"
2{
3	Properties
4	{
5		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
6		_Color("Tint", Color) = (1,1,1,1)
7		_WobblesPerSecond("Wobbles per Second ", Float) = 1
8		_WobblePeriod("Wobble Period (pixels)", Float) = 1
9		_WobbleAmplitude("Wobble Amplitude (pixels)", Float) = 0.03
10	}
11
12	SubShader
13	{
14		Tags
15		{
16			"Queue" = "Transparent"
17			"IgnoreProjector" = "True"
18			"RenderType" = "Transparent"
19			"PreviewType" = "Plane"
20			"CanUseSpriteAtlas" = "True"
21		}
22
23		Cull Off
24		Lighting Off
25		ZWrite Off
26		ZTest[unity_GUIZTestMode]
27		Blend SrcAlpha OneMinusSrcAlpha
28
29		Pass
30		{
31			CGPROGRAM
32			#pragma vertex vert
33			#pragma fragment frag
34
35			#include "UnityCG.cginc"
36			#include "UnityUI.cginc"
37
38			struct appdata_t
39			{
40				float4 vertex   : POSITION;
41				float4 color    : COLOR;
42				float2 texcoord : TEXCOORD0;
43			};
44
45			struct v2f
46			{
47				float4 vertex   : SV_POSITION;
48				fixed4 color : COLOR;
49				half2 texcoord  : TEXCOORD0;
50			};
51
52			fixed4 _Color;
53			fixed4 _TextureSampleAdd;
54
55			float _WobblesPerSecond;
56			float _WobblePeriod;
57			float _WobbleAmplitude;
58
59			#define PI 3.14159
60			#define TWO_PI 6.28318
61			v2f vert(appdata_t IN)
62			{
63				v2f OUT;
64				float4 worldPos = IN.vertex;
65				worldPos.y += sin((_Time.y + frac(worldPos.x)) * _WobblesPerSecond * TWO_PI + worldPos.x / _WobblePeriod) * _WobbleAmplitude;
66				worldPos.x += cos((_Time.y + frac(worldPos.x)) * _WobblesPerSecond * TWO_PI + worldPos.x / _WobblePeriod) * _WobbleAmplitude;
67				OUT.vertex = UnityObjectToClipPos(worldPos);
68				OUT.texcoord = IN.texcoord;
69
70		#ifdef UNITY_HALF_TEXEL_OFFSET
71				OUT.vertex.xy += (_ScreenParams.zw - 1.0)*float2(-1,1);
72		#endif
73				OUT.color = IN.color * _Color;
74				return OUT;
75			}
76
77			sampler2D _MainTex;
78			fixed4 frag(v2f IN) : SV_Target
79			{
80				return (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
81			}
82			ENDCG
83		}
84	}
85}