-100p

-10p

+10p

+100p

Unityウェーブシェーダー

UnityでuGUI用ウェーブシェーダー。サイン波とコサイン波で画面を歪ませる実装。
(Wave Shader for Unity uGUI to distorts the screen with sine and cosine waves)

関連ページ
カーテンシェーダー を作る過程でサイン波とコサイン波という大変便利な関数を見つけた。
結果的にカーテンシェーダーでは採用しなかったが、これを使えば簡単にドラクエのワープのような演出が作れたのでそれを共有。

ウェーブシェーダー、コード全文

下がウェーブシェーダーのサンプル動画。
プラスのサイン波、マイナスのサイン波、プラスのコサイン波、マイナスのコサイン波と、最大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つのトグルは、その波形を使うかどうかを表している。
 UseSin1 
: プラスのサイン波を使うかどうか。
 UseSin2 
: マイナスのサイン波を使うかどうか。
 UseCos1 
: プラスのコサイン波を使うかどうか。
 UseCos2 
: マイナスのコサイン波を使うかどうか。

後半の3つは、波形の間隔や振れ幅を操作する。
 Twist 
: 大きいほど波形の間隔が細かくなる。0で全く揺れない。
 SinWave 
: 大きいほどサイン波の揺れ幅が小さくなる。
 CosWave 
: 大きいほどコサイン波の揺れ幅が小さくなる。

コードを少し解説

まずシェーダーの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

-100p

-10p

+10p

+100p