-100p

-10p

+10p

+100p

Unityゆらぎシェーダー

Unity uGUI用、ゆらぎシェーダー。水面や水中の表現に使えるシェーダー。
(Fluctuation Shader for Unity uGUI)

関連ページ
参考URL
水面や陽炎の表現が出来る、画面がゆらゆら揺らぐようなシェーダーが作りたいなと思ってしばらく研究を続けていた。
幸いこういった表現は既にネットにいくつか記事が転がっており、それを元にuGUI用に調整することができたので共有。

似たカテゴリで ウェーブシェーダー もオススメ。
後にこのシェーダーを活かして 陽炎シェーダー も作ってるのでそちらもどうぞ。

ゆらぎシェーダー、コード全文

今回のシェーダーを実装したサンプル動画は次の通り。
左側のドラゴンに関しては、 シルエットシェーダー を併用してちょっと水色に画像を上書きしている。


以下コード全文。uGUI Imageへの反映方法は こちらの記事 参照。
今回の場合GrabPassを使っているので、 こちら の知識も必要になる。
Shader "UI/Fluctuation"
{
    Properties
    {
        [HideInInspector]_MainTex("-",2D)="white"{} 
        
        _FluctPow("FluctPow", Range(0.01, 0.1)) = 0.05 
//歪みの強さ

        _AnimSpeed("AnimSpeed", Range(0.1, 10)) = 0.2 
//歪みが変化するスピード


        [Space(10)]
        [Toggle] _UseAutoAnim("UseAutoAnim", float) = 1 
//経過時間によって自動でアニメーションをするか

        _ManualAnimVal("ManualAnimVal", float) = 0 
//AutoAnimを使わない場合の、アニメの移動値

    }

    SubShader
    {
        Tags 
        { 
            "Queue" = "Transparent"
            "RenderType"="Transparent"
        }
        Cull Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 100

        GrabPass{}

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;            
            };

            struct v2f
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;             
            };

            sampler2D _GrabTexture;

            float _FluctPow;
            float _AnimSpeed;
            float _UseAutoAnim;
            float _ManualAnimVal;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = ComputeGrabScreenPos(o.vertex);
                return o;
            }

            
//ランダムノイズ(粗いノイズ、0~1の値がランダムに広がる)

            float random (fixed2 p) { 
                p = fixed2(dot(p, float2(127.1, 311.7)),
                    dot(p, fixed2(269.5, 183.3)));

                return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
            }
            
            
//パーリンノイズ(隣接する値が少しずつ異なる滑かなノイズ、ランダムノイズを参照する)

            float perlinNoise(fixed2 st)
            {
                fixed2 p = floor(st);
                fixed2 f = frac(st);
                fixed2 u = f * f * (3.0 - 2.0 * f);

                float v00 = random(p + fixed2(0, 0));
                float v10 = random(p + fixed2(1, 0));
                float v01 = random(p + fixed2(0, 1));
                float v11 = random(p + fixed2(1, 1));

                return lerp(lerp(dot(v00, f - fixed2(0, 0)), dot(v10, f - fixed2(1, 0)), u.x),
                    lerp(dot(v01, f - fixed2(0, 1)), dot(v11, f - fixed2(1, 1)), u.x),
                    u.y) + 0.5f;
            }

            
//fBmノイズ(究極に滑らかなノイズ、パーリンノイズを参照する)

            float fBm (fixed2 st) 
            {
                float f = 0;
                fixed2 q = st;
    
                f += 0.5000*perlinNoise( q ); q = q*2.01;
                f += 0.2500*perlinNoise( q ); q = q*2.02;
                f += 0.1250*perlinNoise( q ); q = q*2.03;
                f += 0.0625*perlinNoise( q ); q = q*2.01;
    
                return f;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 nUv = i.uv;
                
//UseAutoAnimがOnの時は時間経過によってノイズの位置をずらす、Offの時はManualAnimValを参照する

                nUv.y += ((_UseAutoAnim * _Time.y) + ((1 - _UseAutoAnim) * _ManualAnimVal)) * _AnimSpeed;

                
//fBmノイズで滑らかなノイズを作成した上で、値の範囲を0~1から-1~1に拡大

                float noise = 2 * fBm(nUv) -1 ;
                
                
//画面の上下端では歪みを実行させないためのnoise配合率を算出

                fixed isUvBotom = step(i.uv.y, 0.1);
                fixed rate1 = isUvBotom * (i.uv.y / 0.1) + (1 - isUvBotom) * 1;
                fixed isUvUp = 1 - step(i.uv.y, 0.9);
                fixed rate2 = isUvUp * ((0.1 - (i.uv.y - 0.9)) / 0.1) + (1 - isUvUp) * 1;
                fixed noiseRate = rate1 * rate2;

                
//noiseの値は-1~1の範囲に収まっている

                
//これに_FluctPowを掛けると、最小で-0.01~0.01、最大で-0.1~0.1の範囲に収まる

                
//その修正された値で、対象画像のuv座標のy軸だけ移動させ、描画するピクセルをズラす

                i.uv.y += noiseRate * (noise * _FluctPow);

                
//Grabした画像に変形させたuv座標を適用させ歪ませる

                fixed4 col = tex2D(_GrabTexture, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

ゆらぎシェーダーの使い方

プロパティとして4つの値が外出しされている。
[HideInInspector]_MainTexはエラーログ回避のため用意してるだけなんで数に入れない。
    Properties
    {
        [HideInInspector]_MainTex("-",2D)="white"{} 
        
        _FluctPow("FluctPow", Range(0.01, 0.1)) = 0.05 
//歪みの強さ

        _AnimSpeed("AnimSpeed", Range(0.1, 10)) = 0.2 
//歪みが変化するスピード


        [Space(10)]
        [Toggle] _UseAutoAnim("UseAutoAnim", float) = 1 
//経過時間によって自動でアニメーションをするか

        _ManualAnimVal("ManualAnimVal", float) = 0 
//AutoAnimを使わない場合の、アニメの移動値

    }
ゆがみシェーダーのプロパティ

FluctPowを操作すると画像に対する歪みの強さを変更できる。
下は最小値0.01と最大値0.1に設定した時の比較動画。


AnimSpeedを操作すると歪みの変化スピードを変更できる。
下は最小値の0.1と最大値の10に設定した時の比較動画。


UseAutoAnimのトグルがOnの場合、UnityをPlay状態にしてるだけで勝手にゆらぎアニメーションを実行する。
コードで言うと下の部分で、_Time.yというUnity側で用意している変数を使い、シーン読み込みからの経過時間でゆがみを変化させてる。
                
//UseAutoAnimがOnの時は時間経過によってノイズの位置をずらす、Offの時はManualAnimValを参照する

                nUv.y += ((_UseAutoAnim * _Time.y) + ((1 - _UseAutoAnim) * _ManualAnimVal)) * _AnimSpeed;

Unityのドキュメントを読むと、_Timeは4次元の変数で、1~4次元目まで経過時間を少し割ったり掛けたりしてるだけになる。
 _Time 
: float4 : ステージのロードからの時間 (t/20, t, t*2, t*3)。シェーダー内でアニメーション化を行うために使用します。
_Time.yは名前の印象から受けるy軸とかは関係なくて、単純に2次元目のtを指定してるだけ。

UseAutoAnimは初期状態にOnになっていて、もしOffにした場合は、勝手にアニメーションは実行してくれない。
この場合はManualAnimValを操作することで手動でアニメーションを実行する。


勿論 マテリアルにはC#からアクセス可能 なので、C#のコード上からアニメーションも実行できる。下は一例。
    DOTween.To(() => star.material.GetFloat("_ManualAnimVal"), (val) => 
    {
        star.material.SetFloat("_ManualAnimVal", val);
    }, 50, 3f);
1
1

-100p

-10p

+10p

+100p