概要
Unityのテキストをシェーダーを使ってアニメーションさせる方法のまとめ
こんな感じでテキストをアニメーションさせるスクリプトを書いてみました。
https://github.com/katsuma99/TextShaderAnimation
移動アニメーション
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"
}
解説
_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 : 文字数を指定
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 : テキスト番号(右から数えたポリゴン番号)
回転アニメーション
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; // 3点の中間位置を求める
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"
}
解説
[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]) : 描画する頂点を指定
指定した順番に頂点を繋いで面を設定する
スケールアニメーション
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"
}
解説
ポイントは回転アニメーションと同じなので、補足を追記します。
#pragma vertex vert
#pragma geometry geo
#pragma fragment frag
#pragma vertex vert : バーテックスシェーダの関数にvertを指定
頂点単位で処理をする
#pragma geometry geo : ジオメトリシェーダの関数にgeoを指定
プリミティブ単位で処理をする
#pragma fragment frag : フラグメントシェーダの関数にfragを指定
ピクセル単位で処理をする
#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"を用いて描画する
他にも色々
オリジナルなテキストアニメーションを作成してみよう。
使用例
点数が変化する時にテキストアニメーションを行うことで、得点を稼いでいることを実感させる。
android → https://play.google.com/store/apps/details?id=com.secretcommandlab.taptapdrop
iOS → https://appsto.re/jp/fMfpjb.i