Unity uGUI用ウェーブシェーダー。SFCドラクエのワープのような演出。
(Wave Shader for Unity uGUI)
関連ページ
カーテンシェーダー
を作る過程でサイン波とコサイン波という大変便利な関数を見つけた。
結果的にカーテンシェーダーでは採用しなかったが、これを使えば簡単にドラクエのワープのような演出が作れたのでそれを共有。
ウェーブシェーダー、コード全文
下がウェーブシェーダーのサンプル動画。
プラスのサイン波、マイナスのサイン波、プラスのコサイン波、マイナスのコサイン波と、最大4つの波形をブレンドできる。
以下コード全文。uGUI Imageへの反映方法は
こちらの記事
参照。
今回の場合GrabPassを使っているので、
こちら
の知識も必要になる。
Shader
"UI/GrabWave"
{
Properties
{
[HideInInspector]_MainTex("-",2D)="white"{}
//使用する波形をトグルとして表示、実際はfloat値の0か1が保存される
[Toggle]
_UseSin1("UseSin1",
float)
=
1
[Toggle]
_UseSin2("UseSin2",
float)
=
1
[Toggle]
_UseCos1("UseCos1",
float)
=
1
[Toggle]
_UseCos2("UseCos2",
float)
=
1
[Space(10)]
_Twist
("Twist",
Range(0,
100))
=
0
//波形の細かさ
_SinWave("SinWave",
Range(1,
50))
=
5
//サイン波の波形の振れ幅の抑制値
_CosWave("CosWave",
Range(1,
50))
=
10
//コサイン波の波形の振れ幅の抑制値
}
SubShader
{
Tags
{
"Queue"
=
"Transparent"
"RenderType"="Transparent"
}
Cull
Off
ZWrite
Off
Blend
SrcAlpha
OneMinusSrcAlpha
GrabPass{}
Pass
{
CGPROGRAM
#pragma
vertex
vert
#pragma
fragment
frag
#include
"UnityCG.cginc"
sampler2D
_GrabTexture;
float
_UseSin1;
float
_UseSin2;
float
_UseCos1;
float
_UseCos2;
float
_Twist;
float
_SinWave;
float
_CosWave;
struct
appdata
{
fixed4
vertex
:
POSITION;
fixed4
uv
:
TEXCOORD0;
};
struct
v2f
{
fixed4
vertex
:
SV_POSITION;
fixed2
uv
:
TEXCOORD0;
};
v2f
vert(appdata
i)
{
v2f
o;
o.vertex
=
UnityObjectToClipPos(i.vertex);
o.uv
=
ComputeGrabScreenPos(o.vertex);
return
o;
}
//プラスのサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
sinUv1(v2f
i)
{
fixed
twist
=
sin(i.uv.y
*
_Twist)
/
_SinWave;
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
//マイナスのサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
sinUv2(v2f
i)
{
fixed
twist
=
-sin(i.uv.y
*
_Twist)
/
_SinWave;
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
//プラスのコサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
cosUv1(v2f
i)
{
fixed
twist
=
cos(i.uv.y
*
_Twist)
/
_CosWave
-
(1
/
_CosWave);
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
//マイナスのコサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
cosUv2(v2f
i)
{
fixed
twist
=
-cos(i.uv.y
*
_Twist)
/
_CosWave
+
(1
/
_CosWave);
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
fixed4
frag(v2f
i)
:
SV_Target
{
//4つの波形のx軸修正値を取得、ただし対応する_Use***が0の場合0を取得
fixed4
col1
=
tex2D(_GrabTexture,
sinUv1(i))
*
_UseSin1;
fixed4
col2
=
tex2D(_GrabTexture,
sinUv2(i))
*
_UseSin2;
fixed4
col3
=
tex2D(_GrabTexture,
cosUv1(i))
*
_UseCos1;
fixed4
col4
=
tex2D(_GrabTexture,
cosUv2(i))
*
_UseCos2;
//それぞれの波系の合成率を取得、2種類使ってれば0.5を、4種類使ってれば0.25を返す
fixed
division
=
1
/
(_UseSin1
+
_UseSin2
+
_UseCos1
+
_UseCos2);
//それぞれの波形に合成率を掛ける
col1
=
col1
*
division;
col2
=
col2
*
division;
col3
=
col3
*
division;
col4
=
col4
*
division;
//波形を合成して返す
return
col1
+
col2
+
col3
+
col4;
}
ENDCG
}
}
}
実装方法としては、uGUIのキャンバスの一番下にこのシェーダーを配置し、widthとheightは画面全体を覆うように設定する。
後はマテリアルから値をいじればOK。もちろんシェーダー変数にはC#からもアクセスできる。
プロパティを解説
用意してあるプロパティは7つ。
最初の4つのトグルは、その波形を使うかどうかを表している。
後半の3つは、波形の間隔や振れ幅を操作する。
コードを少し解説
まずシェーダーのHLSL言語にはsinとcosという、波形を作るのに大変便利な関数がある。
この関数に対していかなる数値を与えても、必ず-1から1の間の値を返す。
しかもその-1から1に行ったり来たりする値は、綺麗なカーブを描いたものが返ってくる。
今回やっていることは、対象ピクセルのuv座標のy軸をsin/cos関数に渡し、返ってきた値をx軸にプラスさせるという手法をとっている。
サイン波に関するコードは下の通り。
//プラスのサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
sinUv1(v2f
i)
{
fixed
twist
=
sin(i.uv.y
*
_Twist)
/
_SinWave;
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
//マイナスのサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
sinUv2(v2f
i)
{
fixed
twist
=
-sin(i.uv.y
*
_Twist)
/
_SinWave;
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
sin(i.uv.y * _Twist)
を見ての通り、実際にはsin関数に対してはy軸をそのまま渡しているわけではない。
_Twistの値をかけて、ユーザーが波形の細かさを操作できるようにしてある。
またsin関数から返ってくる値は最小値が-1で最大値が+1だが、これをxにそのまま加えると波形がデカすぎて不味い。
uv座標は右下が(0, 0)で左上が(1, 1)であるため、このx軸に-1や+1を加えると画面から相当飛び出てしまう。
なので
/ _SinWave
というコードで波形の揺れ幅を抑えている。
コサイン波に関するコードは下の通り。
//プラスのコサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
cosUv1(v2f
i)
{
fixed
twist
=
cos(i.uv.y
*
_Twist)
/
_CosWave
-
(1
/
_CosWave);
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
//マイナスのコサイン波を使ってy軸座標からx軸の修正値を返す
fixed2
cosUv2(v2f
i)
{
fixed
twist
=
-cos(i.uv.y
*
_Twist)
/
_CosWave
+
(1
/
_CosWave);
fixed
resultX
=
i.uv.x
+
twist;
fixed2
fixUv
=
fixed2(resultX
,
i.uv.y);
return
fixUv;
}
やっている事はサイン波とほぼ同じだが、twistの値に対し最後
(1 / _CosWave)
という式を加えている。
説明が難しいが、これはコサインの波が最初に画面外からやってきて演出上違和感があるために、調整のため加えている。
1
1