知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】お絵かきシェーダー【2】 #54

前回の成果

お絵かきシェーダーを勉強した。

soramamenatan.hatenablog.com


今回やること

引き続き、お絵かきシェーダーを学んでいきます。

docs.google.com


事前準備

Scene上にQuadを配置します。

f:id:soramamenatan:20200516162721p:plain


ソースコード

Shader "Unlit/Drawing_1" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            fixed4 frag (v2f i) : SV_Target {
                return float4(i.uv.x, i.uv.y, 0, 1);
            }
            ENDCG
        }
    }
}

このソースのフラグメントシェーダー部分を変えていきます。


ハートを作る

ディスタンスフィールドの概念を利用し、ハートを作ってみようと思います。


ハートの数式

ハートの数式は以下となります。

f:id:soramamenatan:20200520154234p:plain

ピカチュウや初音ミクも!式から面白いグラフを描いてみよう:より引用


ソースコードに反映

これを反映していきます。

fixed4 frag (v2f i) : SV_Target {
    float2 uv = i.uv;
    float heart;
    uv = (uv - float2(0.5, 0.38)) * float2(2.1, 2.8);
    heart = pow(uv.x, 2) + pow(uv.y - sqrt(abs(uv.x)), 2);
    heart = step(heart, 1);
    return heart;
}

そうすると、ハートの形が描画されます。

f:id:soramamenatan:20200520172703p:plain


ハートを動かす

次にハートを動かしていきます。
ソースコードを以下に変更します。

fixed4 frag (v2f i) : SV_Target {
    float2 uv = i.uv;
    float heart;
    uv = (uv - float2(0.5, 0.38)) * float2(2.1, 2.8);
    heart = pow(uv.x, 2) + pow(uv.y - sqrt(abs(uv.x)), 2);
    heart = step(heart, abs(sin(heart * 8 - _Time.w * 2)));
    return heart;
}

stepをTimeで変化させることにより、ハートを動かすことができます。

f:id:soramamenatan:20200520174423g:plain


step(heart, abs(sin(heart * 8 - _Time.w * 2)))

このstepでこのハートの動きになる理由は数式を分解して、グラフを見て頂けると理解しやすいです。

sin(x)のグラフ

f:id:soramamenatan:20200520174910p:plain

sin(x * 8)のグラフ

f:id:soramamenatan:20200520174915p:plain

abs(sin(x * 8))のグラフ

f:id:soramamenatan:20200520174922p:plain

step(x, abs(sin(x * 8)))のグラフ

f:id:soramamenatan:20200520174927p:plain

後はこの歯型のようなグラフがTimeによってこの形のまま移動するので、上のgifのような動きをします。


極座標

極座標とは、原点からの距離である\displaystyle rと角度である\displaystyle \thetaを使い点を表すものです。

f:id:soramamenatan:20200425153442p:plain

直交座標と極座標(2次元)の変換とメリットの比較 | 高校数学の美しい物語:より引用

普段よく使われる\displaystyle (x, y)のような形は直交座標と呼ばれます。

極座標のグラフのイメージは以下となります。

f:id:soramamenatan:20200521153330p:plain

極座標を使用するために、原点からの距離角度を取得していきます。


花を描画する

極座標の考え方を利用して、花を描画します。


原点を変更する

極座標を使用して描画する際には、原点が中央にあると便利なので位置を変更します。

fixed4 frag (v2f i) : SV_Target {
    float2 uv = i.uv;
    uv = 0.5 - uv;
    float d = distance(0, uv);
    return d;
}

0.5からuv値を減算することによって、原点を左下から中央へと変更させます。
これで、原点からの距離を取得できます。

結果

f:id:soramamenatan:20200521154429p:plain


座標を角度に変換

角度を求めるためにatan2を使用します。

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

f:id:soramamenatan:20200505091127j:plain

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

第一引数がyなのと、戻り値は-\pi~ \piなので気をつけてください。
これを利用し、角度を求めます。

static const float PI = 3.14159265;

fixed4 frag (v2f i) : SV_Target {
    float2 uv = i.uv;
    uv = 0.5 - uv;
    float a = atan2(uv.y, uv.x);
    a = (a + PI)/(PI * 2);
    return a;
}
a = (a + PI)/(PI * 2);

上記の部分は、-\pi~ \piを0~1に変換しています。

結果

f:id:soramamenatan:20200521155313p:plain


花の数式を当てはめる

原点からの距離と角度を取得できたので数式を利用して花を描画していきます。
花の数式は以下となっています。

数式
y = abs(cos(x * 3))
グラフ

f:id:soramamenatan:20200521172600p:plain

これを当てはめると以下のようになります。

ソースコード
fixed4 frag (v2f i) : SV_Target {
    float2 uv = i.uv;
    uv = 0.5 - uv;
    float a = atan2(uv.y, uv.x);
    float d = abs(cos(a * 3));
    return d;
}
結果

f:id:soramamenatan:20200521172716p:plain

これは、それぞれの角度で原点からどれだけ離れているかを表しています。
つまり色が黒いと原点から近く、白いと原点から遠くなっています。
これを各ピクセルの原点からの距離をしきい値としてstepしてあげます。

fixed4 frag (v2f i) : SV_Target {
    float2 uv = i.uv;
    uv = 0.5 - uv;
    float a = atan2(uv.y, uv.x);
    // 花が大きく描画されすぎてしまうので*0.4で微調整
    float d = abs(cos(a * 3)) * 0.4;
    d = step(length(uv), d);
    return d;
}
結果

これで花の完成となります。

f:id:soramamenatan:20200521173149p:plain


雪と桜の描画

極座標を利用し、花の他にも雪と桜が表現出来ます。


数式
y = abs(cos(x * 12) * sin(x * 3)) * 0.4 + 0.1
グラフ

f:id:soramamenatan:20200521174626p:plain

結果

f:id:soramamenatan:20200521174628p:plain


数式
y = min(abs(cos(x * 2.5)) + 0.4, abs(sin(x * 2.5)) + 1.1) * 0.32
グラフ

f:id:soramamenatan:20200521174618p:plain

結果

f:id:soramamenatan:20200521174622p:plain


最後に花と雪と桜のソースコードを添付しておきます。

ソースコード

Shader "Unlit/Drawing_5" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            // 花
            // fixed4 frag (v2f i) : SV_Target {
            //     float2 uv = i.uv;
            //     uv = 0.5 - uv;
            //     float a = atan2(uv.y, uv.x);
            //     float d = abs(cos(a * 3)) * 0.4;
            //     d = step(length(uv), d);
            //     return d;
            // }

            // 雪
            // fixed4 frag (v2f i) : SV_Target {
            //     float2 uv = i.uv;
            //     uv = 0.5 - uv;
            //     float a = atan2(uv.y, uv.x);
            //     float d = abs(cos(a * 12) * sin(a * 3)) * 0.4 + 0.1;
            //     d = step(length(uv), d);
            //     return d;
            // }

            // 桜
            fixed4 frag (v2f i) : SV_Target {
                float2 uv = i.uv;
                uv = 0.5 - uv;
                float a = atan2(uv.y, uv.x);
                float d = min(abs(cos(a * 2.5)) + 0.4, abs(sin(a * 2.5)) + 1.1) * 0.32;
                d = step(length(uv), d);
                return d;
            }
            ENDCG
        }
    }
}

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