[Unity uGUI] x軸ウェーブシェーダー。x軸に画面がゆらゆらと揺らぐ演出。
(X Axis Wave Shader)
関連ページ
カーテンシェーダー
を作る過程でサイン波とコサイン波という大変便利な関数を見つけました。
結果的にカーテンシェーダーでは採用しませんでしたが、これを使って良い演出が作れたのでそれを共有します。

x軸ウェーブシェーダー概要
昔のSFCのドラゴンクエストでは、ワープする際にx軸に画面がゆらゆらと揺らぐ演出を使っていました。
下はそのSFCドラゴンクエスト1.2のワープ演出になります。
シェーダーのサイン波とコサイン波を知って、この関数を使えば簡単にドラクエの演出を模倣できそうだと思いました。
下が出来上がったx軸ウェーブシェーダーのサンプル動画です。
プラスのサイン波、マイナスのサイン波、プラスのコサイン波、マイナスのコサイン波と、最大4つの波形をブレンドできます。

x軸ウェーブシェーダー、コード全文
以下コード全文です。uGUI Imageへの反映方法は
こちらの記事
参照してください。
今回の場合GrabPassを使っているので、
こちら
の知識も必要になります。

Shader
"UI/XAxisWave"
{
Properties
{
//使用する波形をトグルとして表示、実際は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