知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】Imageのアウトラインを動かす 【2】#51

前回の成果

アウトラインの元となる部分を回転させた。

soramamenatan.hatenablog.com


今回やること

Imageに合わせたアウトラインを回転させていきます。


事前準備

Scene上にImageを配置します。
Imageに画像と今回制作するマテリアルをアタッチします。
画像はなるべく単純な形のもののほうが綺麗にアウトラインができます。

Sceneのキャプチャ

f:id:soramamenatan:20200505082926p:plain

使用した画像

f:id:soramamenatan:20200505082830p:plain

一円玉のイラスト(お金・硬貨) | かわいいフリー素材集 いらすとや:より引用

ソースコード

Shader "Unlit/rotateOutline" {
    Properties {
        _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
        _BlurColor ("Blur Color", Color) = (1, 1, 1, 1)
        _BlurSize ("Blur Size", float) = 1

        _Speed ("Speed", float) = 1
        _Angle ("Angle", Range(0, 1)) = 1
        _OffSet("xy : offset, zw : notUseing", Vector) = (0.5,0.5,0,0)
    }

    CGINCLUDE
    struct appdata {
        float4 vertex   : POSITION;
        float2 texcoord : TEXCOORD0;
    };

    struct v2f {
        float4 vertex   : SV_POSITION;
        half2 texcoord  : TEXCOORD0;
        float4 worldPosition : TEXCOORD1;
    };

    #include "UnityCG.cginc"

    sampler2D _MainTex;
    float4 _MainTex_TexelSize;
    fixed4 _BlurColor;
    float _BlurSize;
    half _Speed;
    half _Angle;
    fixed4 _OffSet;

    static const float PI = 3.14159265;

    v2f vert (appdata v) {
        v2f o;
        o.worldPosition = v.vertex;
        o.vertex = UnityObjectToClipPos(o.worldPosition);
        o.texcoord = v.texcoord;
        return o;
    }

    fixed4 frag(v2f v) : SV_Target {
        half4 color = (tex2D(_MainTex, v.texcoord));
        return color;
    }

    fixed4 frag_blur (v2f v) : SV_Target {
        int k = 1;
        float2 blurSize = _BlurSize * _MainTex_TexelSize.xy;
        float blurAlpha = 0;
        float2 tempCoord = float2(0,0);
        float tempAlpha;
        for (int px = -k; px <= k; px++) {
            for (int py = -k; py <= k; py++) {
                tempCoord = v.texcoord;
                tempCoord.x += px * blurSize.x;
                tempCoord.y += py * blurSize.y;
                tempAlpha = tex2D(_MainTex, tempCoord).a;
                blurAlpha += tempAlpha;
            }
        }
        half4 blurColor = _BlurColor;
        half timeAngle = _Time.y * _Speed;
        half2x2 rotate = half2x2(cos(timeAngle), -sin(timeAngle), sin(timeAngle), cos(timeAngle));
        float2 offsetUv = v.texcoord - _OffSet.xy;
        offsetUv = mul(rotate, offsetUv);
        half uvAngle = atan2(offsetUv.y, offsetUv.x);
        half tolerance = (-_Angle + 0.5) * 2 * PI;
        int angleStep = step(uvAngle, tolerance);
        offsetUv *= (offsetUv.xy * angleStep);
        offsetUv = step(offsetUv, 0);
        offsetUv = offsetUv * step(0.001, blurAlpha);
        blurColor.a *= offsetUv;
        return blurColor;
    }
    ENDCG

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

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag_blur
            ENDCG
        }

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    }
}

前回のソースコードに数行加えました。


Imageを描画

前回の結果

f:id:soramamenatan:20200425164836g:plain

前回のままですと、Imageがそもそも描画されないので、描画します。

fixed4 frag(v2f v) : SV_Target {
    half4 color = (tex2D(_MainTex, v.texcoord));
    return color;
}
// 省略
Pass {
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    ENDCG
}

ここの部分に関しては特に説明は不要かと思います。

Imageを描画した結果

f:id:soramamenatan:20200505084204g:plain


Imageのアウトラインに添わせる

今のままですと、Imageのテクスチャに関係なく描画されてしまっているので、アウトラインにします。

offsetUv = offsetUv * step(0.001, blurAlpha);

前回の記事で計算したアルファ値が0以上なら描画するようにstepをしています。
こうすることによりアウトラインに添うようになります。

f:id:soramamenatan:20200505085406g:plain

step

引数に応じて0.0か1.0を返す関数です。

// y >= x なら1.0を、x > y なら0.0を返す
step(x, y)

// 1.0が返ってくる
step(0, 1)
// 0.0が返ってくる
step(0.5, 0.1)

一定の角度を描画する

アウトラインの描画が出来ました。
しかし今のままですと、角度が必ず180度になってしまっているので変更出来るようにします。


角度の計算

half uvAngle = atan2(offsetUv.y, offsetUv.x);
half tolerance = (-_Angle + 0.5) * 2 * PI;
int angleStep = step(uvAngle, tolerance);
offsetUv *= (offsetUv.xy * angleStep);

角度を調整しているのは上記の部分です。

まず以下の部分で自身の角度を出しています。

half uvAngle = atan2(offsetUv.y, offsetUv.x);

atan2

atan2は、点(0, 0)から点(x, y)までの半直線と、正のx軸の間の平面上での角度(ラジアン)を返すものです。
以下の画像を見ていただけるとイメージが掴みやすいと思います。

f:id:soramamenatan:20200505091127j:plain

atan2 - cpprefjp C++日本語リファレンス:より引用

第一引数がyなのと、戻り値は-\pi~ \piなので気をつけてください。


自身の角度を出した後は、以下で許容する角度を出してstepしています。

half tolerance = (-_Angle + 0.5) * 2 * PI;
int angleStep = step(uvAngle, tolerance);
offsetUv *= (offsetUv.xy * angleStep);

自分は_Angleを0~1の間に収めたかったのでこうしています。


結果

角度が調整できれば成功です。

描画結果

f:id:soramamenatan:20200505092334g:plain

プロパティ

f:id:soramamenatan:20200505092406p:plain


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

Shader "Unlit/rotateOutline" {
    Properties {
        _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
        _BlurColor ("Blur Color", Color) = (1, 1, 1, 1)
        _BlurSize ("Blur Size", float) = 1

        _Speed ("Speed", float) = 1
        _Angle ("Angle", Range(0, 1)) = 1
        _OffSet("xy : offset, zw : notUseing", Vector) = (0.5,0.5,0,0)
    }

    CGINCLUDE
    // passで共通処理をまとめる
    struct appdata {
        float4 vertex   : POSITION;
        float2 texcoord : TEXCOORD0;
    };

    struct v2f {
        float4 vertex   : SV_POSITION;
        half2 texcoord  : TEXCOORD0;
        float4 worldPosition : TEXCOORD1;
    };

    #include "UnityCG.cginc"

    sampler2D _MainTex;
    // MainTexのサイズを扱う
    // x : 1.0 / width
    // y : 1.0 / height
    // z : width
    // w : height
    float4 _MainTex_TexelSize;
    fixed4 _BlurColor;
    float _BlurSize;
    half _Speed;
    half _Angle;
    fixed4 _OffSet;

    static const float PI = 3.14159265;

    v2f vert (appdata v) {
        v2f o;
        o.worldPosition = v.vertex;
        o.vertex = UnityObjectToClipPos(o.worldPosition);
        o.texcoord = v.texcoord;
        return o;
    }

    fixed4 frag(v2f v) : SV_Target {
        half4 color = (tex2D(_MainTex, v.texcoord));
        return color;
    }

    fixed4 frag_blur (v2f v) : SV_Target {
        // -1 ~ 1でforを回す
        int k = 1;
        float2 blurSize = _BlurSize * _MainTex_TexelSize.xy;
        float blurAlpha = 0;
        float2 tempCoord = float2(0,0);
        float tempAlpha;
        // xy方向にblurSize分ズラし、アルファ値を計算
        for (int px = -k; px <= k; px++) {
            for (int py = -k; py <= k; py++) {
                tempCoord = v.texcoord;
                tempCoord.x += px * blurSize.x;
                tempCoord.y += py * blurSize.y;
                tempAlpha = tex2D(_MainTex, tempCoord).a;
                blurAlpha += tempAlpha;
            }
        }
        half4 blurColor = _BlurColor;
        half timeAngle = _Time.y * _Speed;
        // 回転座標
        half2x2 rotate = half2x2(cos(timeAngle), -sin(timeAngle), sin(timeAngle), cos(timeAngle));
        // 回転の中心座標
        float2 offsetUv = v.texcoord - _OffSet.xy;
        // timeAngle回転させたときの座標
        offsetUv = mul(rotate, offsetUv);
        // 自身の角度
        half uvAngle = atan2(offsetUv.y, offsetUv.x);
        // 許容する角度(0~1)
        half tolerance = (-_Angle + 0.5) * 2 * PI;
        int angleStep = step(uvAngle, tolerance);
        offsetUv *= (offsetUv.xy * angleStep);
        // アルファ値を0or1に
        offsetUv = step(offsetUv, 0);
        offsetUv = offsetUv * step(0.001, blurAlpha);
        blurColor.a *= offsetUv;
        return blurColor;
    }
    ENDCG

    SubShader {
        Tags {
            "Queue"="Transparent"
            // プロジェクターの影響を受けないように
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            // マテリアルのincpecterでの表示をplaneに
            "PreviewType"="Plane"
            // spriteに不具合がある場合にfalseになる
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        // CanvasのRenderModeによって動的に変化
        // Screen Space - Overlay : Always
        // Screen Space - Camera : LEqual
        // World Space : LEqual
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha

        // アウトラインの描画
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag_blur
            ENDCG
        }

        // テクスチャの描画
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }

    }
}

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