Skip to content

Instantly share code, notes, and snippets.

@daniellanner
Last active April 14, 2020 04:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daniellanner/dab6e1b0a01c44bfafd39cec11276e96 to your computer and use it in GitHub Desktop.
Save daniellanner/dab6e1b0a01c44bfafd39cec11276e96 to your computer and use it in GitHub Desktop.
Expanding Button - Unity shader done for a dribbble shot.
/**
(c) 2020 Daniel Lanner
This code is licensed under MIT license, do with it as you will
This shader creates an expanding button effect I used for a loading animation to a screen wipe.
It uses the uv.y coordinates to "expand" the button beyond it's boundaries. This means the geometry width
set within the inspector is not the final output as _IdleContraction reduces the de facto height of the button.
*/
Shader "InDiversity/Unlit Frag/Expanding Button"
{
Properties
{
_PrevTex("Prev Tex", 2D) = "white" {}
_NextTex("Next Tex", 2D) = "white" {}
// expanding wave shape
_IdleContraction("Idle Contraction", Range(0.0, 0.5)) = 0.1 // uv.y width the button defaults to
_HighContraction("High Contraction", Range(0.0, 0.5)) = 0.0 // uv.y value the button expands to for the initial wave
_LowContraction("Low Contraction", Range(0.0, 0.5)) = 0.2 // uv.y value the button contracts to after the initial wave
_HighContractionWidth("Contraction Width", Range(0.0, 1.0)) = 0.1 // uv.x value of the width of the expanding initial wave
// contracted sin shape
_LowContractionFrequency("Low Contraction Frequency", float) = 4
_LowContractionOffset("Low Contraction Offset", float) = 4
_LowContractionSinStrength("Low Contraction Sin Strength", Range(0,1)) = 1
// driving value
_T("Amount to Interpolate", Range(0.0, 1.0)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define PI 3.14159265
#define HALF_PI 1.57079632
#define TWO_PI 6.28318530
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float2 screenpos : TEXCOORD1;
};
sampler2D _PrevTex;
float4 _PrevTex_ST;
sampler2D _NextTex;
float4 _NextTex_ST;
float _IdleContraction;
float _HighContraction;
float _LowContraction;
float _HighContractionWidth;
float _LowContractionFrequency;
float _LowContractionOffset;
float _LowContractionSinStrength;
float _T;
// utility functions
float GreaterThan(float a, float b)
{
return (sign(a - b) + 1.0) / 2.0;
}
float LessThan(float a, float b)
{
return 1.0 - GreaterThan(a, b);
}
float Ease(float x, float a)
{
return pow(x, a) / (pow(x, a) + pow(1.0 - x, a));
}
float SinBellEase(float x)
{
return (sin(-HALF_PI + x * TWO_PI) + 1) * .5;
}
float SinBellEaseWithOffset(float x, float freq, float off)
{
return (sin(-HALF_PI + x * freq * TWO_PI + off) + 1) * .5;
}
float NormalizedInRange(float value, float range_min, float range_max)
{
return saturate((value - range_min) / (range_max - range_min));
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _PrevTex);
o.screenpos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// find state of pixel in uv.x axis
float halfConWidth = _HighContractionWidth * 0.5;
float delta = lerp(_T - halfConWidth, _T + halfConWidth, _T);
float highContraction = _IdleContraction;
highContraction += (_HighContraction - _IdleContraction) * SinBellEase(i.uv.x);
float lowContraction = _LowContraction * SinBellEaseWithOffset(i.uv.x, _LowContractionFrequency, _LowContractionOffset);
lowContraction = lerp(_LowContraction, lowContraction, _LowContractionSinStrength);
float isContracted = LessThan(i.uv.x, delta - halfConWidth);
float isIdle = GreaterThan(i.uv.x, delta + halfConWidth);
float isExpanded = 1.0 - (isContracted + isIdle);
// determine shape based on contraction state
float leftLerp = NormalizedInRange(i.uv.x, delta - halfConWidth, delta);
leftLerp = Ease(leftLerp, 2.0);
float rightLerp = NormalizedInRange(i.uv.x, delta, delta + halfConWidth);
rightLerp = Ease(rightLerp, 2.0);
float isLeftLerp = LessThan(i.uv.x, delta);
float expandedValue =
isLeftLerp * lerp(lowContraction, highContraction, leftLerp) +
(1.0 - isLeftLerp) * lerp(highContraction, _IdleContraction, rightLerp);
// color the pixel based on calculated values
// NOTE: this example is just setting the "non-button" part as the background color
float yRefPoint = abs(i.uv.y - 0.5);
fixed4 bgColor = tex2D(_PrevTex, i.screenpos.xy);
fixed4 frontColor = tex2D(_NextTex, i.screenpos.xy);
fixed4 leftCol = lerp(frontColor, bgColor, GreaterThan(yRefPoint, lowContraction));
fixed4 centerCol = lerp(frontColor, bgColor, GreaterThan(yRefPoint, expandedValue));
fixed4 rightCol = lerp(frontColor, bgColor, GreaterThan(yRefPoint, _IdleContraction));
fixed4 col =
leftCol * isContracted +
rightCol * isIdle +
centerCol * isExpanded;
return col;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment