知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】uGUIのImageを揺らす #79

前回の成果

ShaderLabのプロパティ属性について理解した。

soramamenatan.hatenablog.com


今回やること

uGUIのImageを揺らそうと思います。


事前準備

Scene上にImageを配置し、今回制作するマテリアルをアタッチします。

f:id:soramamenatan:20200912150408p:plain

f:id:soramamenatan:20200912150404p:plain

UnityのShader勉強4 テクスチャを貼る - はるのゲーム開発メモ:より引用


ソースコード

Shader "Unlit/shakeTexture" {
    Properties {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0

        _SinWave("SinWave", Range(0, 1)) = 0.2
        _SinWidth("SinWidth", Range(0, 1)) = 0.5
        _SinSpeed("SinSpeed", Range(0, 1)) = 0.2
        _SinColorDistant("SinColorDistant", Range(0, 3)) = 0.2
        [Toggle] _IsHorizontal ("Is Horizontal", int) = 0
    }

    SubShader {
        Tags {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
            #pragma multi_compile_instancing
            #pragma multi_compile _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA

            #include "UnityCG.cginc"

           #ifdef UNITY_INSTANCING_ENABLED
                UNITY_INSTANCING_BUFFER_START(PerDrawSprite)
                fixed4 unity_SpriteRendererColorArray[UNITY_INSTANCED_ARRAY_SIZE];
                float4 unity_SpriteFlipArray[UNITY_INSTANCED_ARRAY_SIZE];
                UNITY_INSTANCING_BUFFER_END(PerDrawSprite)
               #define _RendererColor unity_SpriteRendererColorArray[unity_InstanceID]
               #define _Flip unity_SpriteFlipArray[unity_InstanceID]
           #endif

            CBUFFER_START(UnityPerDrawSprite)
           #ifndef UNITY_INSTANCING_ENABLED
                fixed4 _RendererColor;
                float4 _Flip;
           #endif
            float _EnableExternalAlpha;
            CBUFFER_END

            fixed4 _Color;

            struct appdata_t {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert(appdata_t v) {
                v2f o;
                UNITY_SETUP_INSTANCE_ID (v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                #ifdef UNITY_INSTANCING_ENABLED
                    v.vertex.xy *= _Flip.xy;
                #endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.texcoord = v.texcoord;
                o.color = v.color * _Color * _RendererColor;
               #ifdef PIXELSNAP_ON
                    o.vertex = UnityPixelSnap (o.vertex);
               #endif
                return o;
            }

            sampler2D _MainTex;
            sampler2D _AlphaTex;
            float _SinWave;
            float _SinWidth;
            float _SinSpeed;
            float _SinColorDistant;
            int _IsHorizontal;

            float _wave;
            float _speed;
            float _width;
            float _clrDis;

            float2 posColor(float2 inUV, float n) {
                float waveValueX = sin(inUV.y * _wave + _speed + _clrDis * n) * _width;
                float waveValueY = sin(inUV.x * _wave + _speed + _clrDis * n) * _width;
                return inUV + float2(waveValueX * step(1, _IsHorizontal), waveValueY * step(_IsHorizontal, 0));
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 color = fixed4(0, 0, 0, 0);
                float2 inUV = i.texcoord;

                _wave = _SinWave * 100;
                _speed = _Time.y * _SinSpeed * 20.0;
                _width = _SinWidth * 0.2;
                _clrDis = _SinColorDistant * _SinWidth * 5;

                color.r = tex2D(_MainTex, posColor(inUV, 2)).r;
                color.g = tex2D(_MainTex, posColor(inUV, 1)).g;
                color.b = tex2D(_MainTex, posColor(inUV, 0)).b;
                color.a = (
                        tex2D(_MainTex, posColor(inUV, 2)).a +
                        tex2D(_MainTex, posColor(inUV, 1)).a +
                        tex2D(_MainTex, posColor(inUV, 0)).a
                    ) / 3;

                color *= i.color;
                color.rgb *= color.a;
                return color;
            }
        ENDCG
        }
    }
}

ShaderはUnityのビルドインシェーダーのUI-Default.shaderを元に作成しています。
ビルドインシェーダーは、以下のUnity公式サイトから取得することができます。

unity3d.com

対象のUnityバージョンを選択して、以下画像のようにダウンロードしてください。

f:id:soramamenatan:20200814150453p:plain


Shaderの解説

今回のShaderは、ただ揺らすだけではなく色も揺らします。


色を揺らす

color.r = tex2D(_MainTex, posColor(inUV, 2)).r;
color.g = tex2D(_MainTex, posColor(inUV, 1)).g;
color.b = tex2D(_MainTex, posColor(inUV, 0)).b;
color.a = (
        tex2D(_MainTex, posColor(inUV, 2)).a +
        tex2D(_MainTex, posColor(inUV, 1)).a +
        tex2D(_MainTex, posColor(inUV, 0)).a
    ) / 3;

色を揺らすと言っても、やっていることは単純です。
各カラーチャンネルのUV値を少しずつズラすことによって、色を揺らす表現を行います


アルファ値に対しても同様の計算を行っているのは、透過画像の対策となります。
この計算をやらないと以下のように、透過部分のアルファ値が意図しないものとなってしまいます。

アルファ値がおかしくなっている例

f:id:soramamenatan:20201108123117g:plain

上記gifで使用した画像(Unityにデフォルトで入っているもの)

f:id:soramamenatan:20201108123116p:plain


posColor関数

float2 posColor(float2 inUV, float n) {
    float waveValueX = sin(inUV.y * _wave + _speed + _clrDis * n) * _width;
    float waveValueY = sin(inUV.x * _wave + _speed + _clrDis * n) * _width;
    return inUV + float2(waveValueX * step(1, _IsHorizontal), waveValueY * step(_IsHorizontal, 0));
}

この関数でズラす値を決めています。
stepとToggleで、縦に揺らすか横に揺らすかを決めています。
sinの揺れの値は以下の画像のように、各色がバラバラになるように引数のnを利用しています。
以下の画像のRBGはグラフの色と同じになります。

f:id:soramamenatan:20201108123543p:plain

step

これはy<=xなら1を、y>xなら0を返す関数です。

step(x, y);

// 1が返ってくる
step(1.0, 1.2);

// 0が返ってくる
step(1.0, 0.8);


posColor関数でズラしたRBGAの値に元の色を乗算してあげることで色の揺れを実現させています。


結果

Imageと色が揺れれば成功です。
色は、A1やA2といった文字の部分がわかりやすいかと思います。

f:id:soramamenatan:20201108123813g:plain

inspector

f:id:soramamenatan:20201108123825p:plain

また、Is Horizontalにチェックを入れることで横揺れにすることも可能です。

f:id:soramamenatan:20201108123856g:plain

念の為、透過画像のgifも添付します。

f:id:soramamenatan:20201108123834g:plain


ソースコードにコメントを付与

Shader "Unlit/shakeTexture" {
    Properties {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0

        _SinWave("SinWave", Range(0, 1)) = 0.2
        _SinWidth("SinWidth", Range(0, 1)) = 0.5
        _SinSpeed("SinSpeed", Range(0, 1)) = 0.2
        _SinColorDistant("SinColorDistant", Range(0, 3)) = 0.2
        [Toggle] _IsHorizontal ("Is Horizontal", int) = 0
    }

    SubShader {
        Tags {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
            #pragma multi_compile_instancing
            #pragma multi_compile _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA

            #include "UnityCG.cginc"

           #ifdef UNITY_INSTANCING_ENABLED
                UNITY_INSTANCING_BUFFER_START(PerDrawSprite)
                fixed4 unity_SpriteRendererColorArray[UNITY_INSTANCED_ARRAY_SIZE];
                float4 unity_SpriteFlipArray[UNITY_INSTANCED_ARRAY_SIZE];
                UNITY_INSTANCING_BUFFER_END(PerDrawSprite)
               #define _RendererColor unity_SpriteRendererColorArray[unity_InstanceID]
               #define _Flip unity_SpriteFlipArray[unity_InstanceID]
           #endif

            CBUFFER_START(UnityPerDrawSprite)
           #ifndef UNITY_INSTANCING_ENABLED
                fixed4 _RendererColor;
                float4 _Flip;
           #endif
            float _EnableExternalAlpha;
            CBUFFER_END

            fixed4 _Color;

            struct appdata_t {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert(appdata_t v) {
                v2f o;
                UNITY_SETUP_INSTANCE_ID (v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                #ifdef UNITY_INSTANCING_ENABLED
                    v.vertex.xy *= _Flip.xy;
                #endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.texcoord = v.texcoord;
                o.color = v.color * _Color * _RendererColor;
               #ifdef PIXELSNAP_ON
                    o.vertex = UnityPixelSnap (o.vertex);
               #endif
                return o;
            }

            sampler2D _MainTex;
            sampler2D _AlphaTex;
            float _SinWave;
            float _SinWidth;
            float _SinSpeed;
            float _SinColorDistant;
            int _IsHorizontal;

            float _wave;
            float _speed;
            float _width;
            float _clrDis;

            /// <summary>
            /// UV値を係数に応じてズラす
            /// </summary>
            /// <param name="inUV">UV値</param>
            /// <param name="n">指数</param>
            float2 posColor(float2 inUV, float n) {
                float waveValueX = sin(inUV.y * _wave + _speed + _clrDis * n) * _width;
                float waveValueY = sin(inUV.x * _wave + _speed + _clrDis * n) * _width;
                // stepで縦に揺れるか横に揺れるかを決める
                return inUV + float2(waveValueX * step(1, _IsHorizontal), waveValueY * step(_IsHorizontal, 0));
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 color = fixed4(0, 0, 0, 0);
                float2 inUV = i.texcoord;

                _wave = _SinWave * 100;
                _speed = _Time.y * _SinSpeed * 20.0;
                _width = _SinWidth * 0.2;
                _clrDis = _SinColorDistant * _SinWidth * 5;

                // 各色をズラす
                color.r = tex2D(_MainTex, posColor(inUV, 2)).r;
                color.g = tex2D(_MainTex, posColor(inUV, 1)).g;
                color.b = tex2D(_MainTex, posColor(inUV, 0)).b;
                // 透過画像対策でアルファ値もズレに合わせる
                color.a = (
                        tex2D(_MainTex, posColor(inUV, 2)).a +
                        tex2D(_MainTex, posColor(inUV, 1)).a +
                        tex2D(_MainTex, posColor(inUV, 0)).a
                    ) / 3;

                color *= i.color;
                color.rgb *= color.a;
                return color;
            }
        ENDCG
        }
    }
}

今回は以上となります。
ここまでご視聴ありがとうございました。

参考サイト様

b.i-tach.com