[Unity]TextShaderAnimationまとめ

# <i class="fa fa-cubes" style="font-size:1em;"></i> 概要
Unityのテキストをシェーダーを使ってアニメーションさせる方法のまとめ
![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/6b49a722-b750-5110-e08c-729727027029.gif)


こんな感じでテキストをアニメーションさせるスクリプトを書いてみました。
<i class="fa fa-cloud-download" style="font-size:1em;"></i> https://github.com/katsuma99/TextShaderAnimation


#<i class="fa fa-cube" style="font-size:1em;"></i> 移動アニメーション
![demo1.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/6122f782-ccb5-ee8d-efd6-d99eb188eb2a.gif)

```csharp:TextMoveAnimation.shader
Shader "Custom/TextMoveAnimation" {
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Color("Color", Color) = (1,1,1,1)
		_Power("Power", Float) = 1
		_NormalTime("NormalizationTime", Float) = 0
		_TextCount("TextCount", Int) = 1
		[MaterialToggle] PixelSnap("Pixel snap", Float) = 1
	}

	SubShader
	{
		Cull Back
		Lighting Off
		AlphaToMask On

		Pass
		{
			CGPROGRAM

#pragma target 3.5
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile PIXELSNAP_ON
#include "UnityCG.cginc"

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				uint vertexId : SV_VertexID;
			};

			struct v2f
			{
				float4 pos   : SV_POSITION;
				fixed4 color : COLOR;
				half2 texcoord  : TEXCOORD0;
			};

			fixed4 _Color;
			float _Power;
			int _TextCount;
			float _NormalTime;

			float4 TransportInterval(float4 pos, uint vertexId) {

				uint geometoryId = vertexId / 4;
				uint textId = ((_TextCount - 1) - geometoryId);
				pos.x -= textId * 10 * _Power * _NormalTime;

				return pos;
			}

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				float4 pos = IN.vertex;
				pos = TransportInterval(pos, IN.vertexId);

				OUT.pos = UnityObjectToClipPos(pos);
				OUT.texcoord = IN.texcoord;
				OUT.color = _Color;

				return OUT;
			}

			sampler2D _MainTex;
			fixed4 frag(v2f IN) : SV_Target
			{
				fixed4 c = tex2D(_MainTex, IN.texcoord);
				c.rgb = IN.color;
				c.rgb *= c.a;

				return c;
			}
			ENDCG
		}
	}
	Fallback "GUI/Text Shader"
}
```

##<i class="fa fa-angle-double-right" style="font-size:1em;"></i> 解説

```csharp
		_Color("Color", Color) = (1,1,1,1)
		_Power("Power", Float) = 1
		_NormalTime("NormalizationTime", Float) = 0
		_TextCount("TextCount", Int) = 1
```

*_Color* : テキストの色を指定
*_Power* : アニメーションの影響度合
*_NormalTime* : アニメーションの時間（0 noPower　1 maxPower）
　1 -> 0に変化するように設定すると広がって元の状態に戻るアニメーションになる
*_TextCount* : 文字数を指定



```csharp
			float4 TransportInterval(float4 pos, uint vertexId) {

				uint geometoryId = vertexId / 4;
				uint textId = ((_TextCount - 1) - geometoryId);
				pos.x -= textId * 10 * _Power * _NormalTime;

				return pos;
			}
```
*vertexId* : 描画するポリゴンの頂点番号
　SV_VertexIDのセマンティクスで取得する
*geometoryId* : ポリゴン番号
*textId* : テキスト番号（右から数えたポリゴン番号）
![ポリゴン番号.PNG](https://qiita-image-store.s3.amazonaws.com/0/183676/d3d241da-c452-d954-da4a-ec2948dda959.png)



#<i class="fa fa-cube" style="font-size:1em;"></i> 回転アニメーション
![demo2.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/fb4c70b5-167c-5f31-79bd-b0e75f3e835e.gif)

```csharp:TextRotateAnimation.shader
Shader "Custom/TextRotateAnimation" {
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Color("Color", Color) = (1,1,1,1)
		_Power("Power", Float) = 1
		_NormalTime("NormalizationTime", Float) = 0
		_TextCount("TextCount", Int) = 1
		[MaterialToggle] PixelSnap("Pixel snap", Float) = 1
	}

	SubShader
	{
		Cull Back
		Lighting Off
		AlphaToMask On

		Pass
		{
			CGPROGRAM

#pragma target 4.0
#pragma vertex vert
#pragma geometry geo
#pragma fragment frag
#pragma multi_compile PIXELSNAP_ON
#include "UnityCG.cginc"

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				uint vertexId : SV_VertexID;
			};

			struct geomIn
			{
				float4 pos   : SV_POSITION;
				fixed4 color : COLOR;
				half2 texcoord  : TEXCOORD0;
			};

			struct v2f
			{
				float4 pos   : SV_POSITION;
				fixed4 color : COLOR;
				half2 texcoord  : TEXCOORD0;
			};

			fixed4 _Color;
			float _Power;
			int _TextCount;
			float _NormalTime;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				OUT.pos = UnityObjectToClipPos(IN.vertex);
				OUT.texcoord = IN.texcoord;
				OUT.color = _Color;
				return OUT;
			}

			float4 CalculateCenter(float s, float t, float4 pos1, float4 pos2, float4 pos3) {
				float4 pos = pos1 + (pos2 - pos1) * s + (pos3 - pos1) * t; // ３点の中間位置を求める
				return pos;
			}

			float4 Rotate(float4 pos, uint vertexId) {

				//画面の中心から回転する
				float Deg2Rad = 0.0174532924;
				int id = vertexId % 8;

				//最大角度(場所によって変える)
				float rotate = _NormalTime * _Power * 50;
				if (id == 0 || id == 1)
					rotate *= 0.3 + abs(_CosTime.z) * 0.8;
				else if (id == 2 || id == 3)
					rotate *= 0.2 + abs(_CosTime.z) * 0.1;
				else if (id == 6 || id == 7)
					rotate *= 0.3 + abs(_SinTime.w) * 0.2;
				float rad = rotate * Deg2Rad;

				//角度算出
				float4 newPos = pos;
				newPos.x = pos.x * cos(rad) - pos.y * sin(rad);
				newPos.y = pos.x * sin(rad) + pos.y * cos(rad);
				return newPos;
			}

			[maxvertexcount(3)]
			void geo(triangle geomIn In[3], uint primitiveId : SV_PrimitiveID, inout TriangleStream<v2f> TriStream) {

				//回転
				float4 center = (In[0].pos - In[2].pos) * 0.5 + In[2].pos;

				In[0].pos -= center;
				In[1].pos -= center;
				In[2].pos -= center;

				In[0].pos = Rotate(In[0].pos, primitiveId);
				In[1].pos = Rotate(In[1].pos, primitiveId);
				In[2].pos = Rotate(In[2].pos, primitiveId);

				In[0].pos += center;
				In[1].pos += center;
				In[2].pos += center;

				TriStream.Append(In[0]);
				TriStream.Append(In[1]);
				TriStream.Append(In[2]);

				// 連続したトライアングルを終了
				TriStream.RestartStrip();
			}

			sampler2D _MainTex;

			fixed4 frag(v2f IN) : SV_Target
			{
				fixed4 c = tex2D(_MainTex, IN.texcoord);
				c.rgb = IN.color;
				c.rgb *= c.a;
				return c;
			}
			ENDCG
		}
	}
	Fallback "GUI/Text Shader"
}
```

##<i class="fa fa-angle-double-right" style="font-size:1em;"></i> 解説




```csharp
			[maxvertexcount(3)]
			void geo(triangle geomIn In[3], uint primitiveId : SV_PrimitiveID, inout TriangleStream<v2f> TriStream) {

				//回転
				float4 center = (In[0].pos - In[2].pos) * 0.5 + In[2].pos;

				//省略

				TriStream.Append(In[0]);
				TriStream.Append(In[1]);
				TriStream.Append(In[2]);

				// 連続したトライアングルを終了
				TriStream.RestartStrip();
			}
```
*[maxvertexcount(3)]* : プリミティブの頂点数を指定
　テキストは三角形ポリゴン2枚で1文字を描画するプレートを作っている
*triangle geomIn In[3]* : ジオメトリ頂点を取得
　指定したプリミティブの頂点数以上は取得できない
*primitiveId* : プリミティブの番号
*center* : 回転させるための中心を計算
　ジオメトリ頂点番号0と2の線分の中点
*TriStream.Append(In[0])* : 描画する頂点を指定
　指定した順番に頂点を繋いで面を設定する
　![プリミティブ番号.PNG](https://qiita-image-store.s3.amazonaws.com/0/183676/700ab86a-5f87-0100-5da0-45a1b82186ed.png)




#<i class="fa fa-cube" style="font-size:1em;"></i> スケールアニメーション
![demo3.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/8756b35d-59de-b4c1-45e0-3cd133166b94.gif)

```csharp:TextScaleAnimation.shader
Shader "Custom/TextScaleAnimation" {
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Color("Color", Color) = (1,1,1,1)
		_Power("Power", Float) = 1.5
		_NormalTime("NormalizationTime", Float) = 0
		_TextCount("TextCount", Int) = 1
		[MaterialToggle] PixelSnap("Pixel snap", Float) = 1
	}

	SubShader
	{

		Cull Back
		Lighting Off
		AlphaToMask On

		Pass
		{
			CGPROGRAM

#pragma target 4.0
#pragma vertex vert
#pragma geometry geo
#pragma fragment frag
#pragma multi_compile PIXELSNAP_ON
#include "UnityCG.cginc"

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				uint vertexId : SV_VertexID;
			};

			struct geomIn
			{
				float4 pos   : SV_POSITION;
				fixed4 color : COLOR;
				half2 texcoord  : TEXCOORD0;
			};

			struct v2f
			{
				float4 pos   : SV_POSITION;
				fixed4 color : COLOR;
				half2 texcoord  : TEXCOORD0;
			};

			fixed4 _Color;
			float _Power;
			int _TextCount;
			float _NormalTime;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				float4 pos = IN.vertex; // Scale(IN.vertex, IN.vertexId);
				OUT.pos = UnityObjectToClipPos(pos);
				OUT.texcoord = IN.texcoord;
				OUT.color = _Color;
				return OUT;
			}

			[maxvertexcount(3)]
			void geo(triangle geomIn In[3], uint primitiveId : SV_PrimitiveID, inout TriangleStream<v2f> TriStream) {
				
				//拡大
				float4 center = (In[0].pos - In[2].pos) * 0.5 + In[2].pos;
				float power = _NormalTime * _Power;
				int id = primitiveId % 10;
				if (id == 2 || id == 3)
					power *= 0.1 + abs(_CosTime.w) * 0.3;
				else if (id == 4 || id == 5)
					power *= 0.8 - abs(_SinTime.y) * 0.5;
				else if (id == 6 || id == 7)
					power *= 0.2 + abs(_SinTime.y) * 0.4;
				else if (id == 8 || id == 9)
					power *= 0.7 - abs(_CosTime.w) * 0.6;

				In[0].pos = (In[0].pos - center) * (power + 1) + center;
				In[1].pos = (In[1].pos - center) * (power + 1) + center;
				In[2].pos = (In[2].pos - center) * (power + 1) + center;

				TriStream.Append(In[0]);
				TriStream.Append(In[1]);
				TriStream.Append(In[2]);

				// 連続したトライアングルを終了
				TriStream.RestartStrip();
			}

			sampler2D _MainTex;

			fixed4 frag(v2f IN) : SV_Target
			{
				fixed4 c = tex2D(_MainTex, IN.texcoord);
				c.rgb = IN.color;
				c.rgb *= c.a;
				return c;
			}
			ENDCG
		}
	}
	Fallback "GUI/Text Shader"
}
```

##<i class="fa fa-angle-double-right" style="font-size:1em;"></i> 解説
ポイントは回転アニメーションと同じなので、補足を追記します。

```csharp
#pragma vertex vert
#pragma geometry geo
#pragma fragment frag
```

*#pragma vertex vert* : バーテックスシェーダの関数にvertを指定
　頂点単位で処理をする
*#pragma geometry geo* : ジオメトリシェーダの関数にgeoを指定
　プリミティブ単位で処理をする
*#pragma fragment frag* : フラグメントシェーダの関数にfragを指定
　ピクセル単位で処理をする

```csharp
#pragma target 3.5

　//省略

Fallback "GUI/Text Shader"
```
*#pragma target 3.5* : 対象にするシェーダーモデルを指定（デフォルト：2.5）
　SV_VertexID：3.5以上必要
　ジオメトリシェーダ：4.0以上必要
*Fallback "GUI/Text Shader"* : シェーダーモデルが対応していない場合は"GUI/Text Shader"を用いて描画する



#<i class="fa fa-list" style="font-size:1em;"></i> 他にも色々
![demoEx3.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/62b4ff02-ccf1-6c8a-4c8a-b72867a0623b.gif)

![demo5.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/c9e711d1-a03f-24e7-9a75-a68bfc4b6c1a.gif)

![demoEx4.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/4a0164d4-4e7f-9939-561b-cfac9ab21b49.gif)

![demo6.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/077f02c6-66c6-b659-8443-2292371b7039.gif)

オリジナルなテキストアニメーションを作成してみよう。

# <i class="fa fa-thumb-tack" style="font-size:1em;"></i> 使用例
点数が変化する時にテキストアニメーションを行うことで、得点を稼いでいることを実感させる。

[![usecase.gif](https://qiita-image-store.s3.amazonaws.com/0/183676/6d8930cc-541d-b70c-82c2-5c546c1cfc02.gif "usecase.gif")](https://www.youtube.com/watch?v=PryO-sxYbkw)

<i class="fa fa-cloud-download" style="font-size:1em;"></i>　android → https://play.google.com/store/apps/details?id=com.secretcommandlab.taptapdrop 
　　iOS → https://appsto.re/jp/fMfpjb.i




#<i class="fa fa-book" style="font-size:1em;"></i> 参考資料
- [Unity - マニュアル: Vertex and fragment shader examples](https://docs.unity3d.com/jp/current/Manual/SL-VertexFragmentShaderExamples.html)
- [組み込み関数 (DirectX HLSL)](https://msdn.microsoft.com/ja-jp/library/ee418317(v=vs.85).aspx)
- [Unity - マニュアル: 内蔵のシェーダー include ファイル](https://docs.unity3d.com/jp/current/Manual/SL-BuiltinIncludes.html)
- [Unity Shader まとめ - ゆにてく備忘録](http://unitech.hatenablog.com/entry/2015/03/11/001953)
- [Unity - マニュアル: シェーダーセマンティクス](https://docs.unity3d.com/jp/current/Manual/SL-ShaderSemantics.html)
- [ストリーム出力ステージの基礎知識 (Direct3D 10)](https://msdn.microsoft.com/ja-jp/library/bb205122(v=vs.85).aspx)
- [ポリゴンの表裏の判定方法](http://www.sousakuba.com/Programming/gs_polygon_inside_outside.html)