First of all, in order to do and show the effect of this example I’ve borrowed two amazing artworks from Arturo Bermudez and Lisa Parsanova.
The goals of this article is to show the benefits of thinking about effects in pre-production stage.
For example, in a lot of 2d/3d mobile games where you have 2d character faces are mostly static images. Most of the times the idea of animating those character faces gets discarded due to the cost in production and the low benefits you get from it in exchange.
Imagine having to create a frame by frame old style animation or having to generate the mesh, skin and animate each face… the cost and the people you will need increments a lot (and the communication needed to achieve it).
In this example I am showing you a very simple shader with only one texture which is used as a displacement texture. The R and G channels are used to codify one animation (vertical and horizontal axis) and the B and A channels to codify another one. Having this two animation codified in the same texture will let you have a loop breathing animation and in top a face expression animation, desync on from the other creating a nice organic movement.
Of course per each face you will need to draw the displacement texture but, isn’t look interesting? how simplified the process gets…
The key of this article is not the effect itself but the fact we should really find the balance between the income and outcome at the time we create technology/effects. How many times we’ve seen very over engineering effects which end up being discarded…
Shader "UI/DisplacementChannelsImage" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _DisplacementTexture ("Displacement Text", 2D) = "gray" {} _DisplacementAmount ("_DisplacementAmount", Range(0.0,0.1)) = 0 _DisplacementAmountFace("_DisplacementAmount", Range(0.0,0.1)) = 0 _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 __ 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 }; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; fixed _DisplacementAmount; fixed _DisplacementAmountFace; v2f vert(appdata_t IN) { v2f OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.worldPosition = IN.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; return OUT; } sampler2D _MainTex; sampler2D _DisplacementTexture; fixed4 frag(v2f IN) : SV_Target { half displacementX = (tex2D(_DisplacementTexture, IN.texcoord).r - 0.5).r * _DisplacementAmount; half displacementY = (tex2D(_DisplacementTexture, IN.texcoord).g - 0.5) * _DisplacementAmount; half displacementXFace = (tex2D(_DisplacementTexture, float2(IN.texcoord.x + displacementX,IN.texcoord.y + displacementY)).b - 0.5) * _DisplacementAmountFace; half displacementYFace = (tex2D(_DisplacementTexture, float2(IN.texcoord.x + displacementX,IN.texcoord.y + displacementY)).a - 0.5) * _DisplacementAmountFace; half4 color = (tex2D(_MainTex, float2(IN.texcoord.x + displacementX +displacementXFace,IN.texcoord.y + displacementY +displacementYFace) + _TextureSampleAdd) * IN.color); color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif return color; } ENDCG } } }