知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】お絵かきシェーダー【4】 #56

前回の成果

歪みを利用してお絵かきした。

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
        }
    }
}

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


繰り返し

画面に複数同じ表現を繰り返し行っていこうと思います。


表現方法

繰り返しは、frac関数を使用することで表現することが出来ます。
frac関数は、

// 小数値の小数部分を返す
frac(x);

// 0.5が返ってくる
frac(1.5);
// 0.1が返ってくる
frac(0.1);

となっています。


まずはuv値をそのまま描画します。

ソースコード
fixed4 frag (v2f i) : SV_Target {
    float2 repet = frac(i.uv);
    return float4(repet.x, repet.y, 0, 1);
}
結果

f:id:soramamenatan:20200606162321p:plain

uv値が描画されましたが、もちろん繰り返しはされていません。
そこでfrac関数に渡すuv値の値を2倍にしてみます。

ソースコード
fixed4 frag (v2f i) : SV_Target {
    // uv値を2倍にする
    int num = 2;
    float2 repet = frac(i.uv * num);
    return float4(repet.x, repet.y, 0, 1);
}
結果

f:id:soramamenatan:20200606162646p:plain

これで繰り返すことが出来ました。

これでなぜ繰り返されているかの理由を説明します。
まず、uv値をnum倍(今回の場合は2倍)しているので、uv座標は(0.0, 0.0)〜(2.0, 2.0)となります。
そこで小数部分に注目して頂きたいのですが、小数部分は0〜9を2週することになります。
ですので、2回繰り返されて描画されることになります。

イメージ

f:id:soramamenatan:20200606163436p:plain

上記の場合は2倍なので2回繰り返されていますが、10倍にすると10回繰り返されます。

ソースコード
fixed4 frag (v2f i) : SV_Target {
    // uv値を10倍にする
    int num = 10;
    float2 repet = frac(i.uv * num);
    return float4(repet.x, repet.y, 0, 1);
}
結果

f:id:soramamenatan:20200607144612p:plain


では次にuv値で描画するのではなく、四角形を描画してみます。
四角形を描画するbox関数を使用して、繰り返し描画します。

ソースコード
// 四角形を描画する
float box (float2 uv, float size) {
    size = 0.5 + size * 0.5;
    uv = step(uv, size) * step(1.0 - uv, size);
    return uv.x * uv.y;
}

fixed4 frag (v2f i) : SV_Target {
    int num = 10;
    float2 repeat = frac(i.uv * num);
    return box(repeat, 0.7);
}
結果

f:id:soramamenatan:20200607145310p:plain

これもfrac関数を使用することによって繰り返し描画することが出来ました。
次に、アニメーションさせてみます。

ソースコード
// 四角形を描画する
float box (float2 uv, float size) {
    size = 0.5 + size * 0.5;
    uv = step(uv, size) * step(1.0 - uv, size);
    return uv.x * uv.y;
}

fixed4 frag (v2f i) : SV_Target {
    int num = 10;
    float2 repeat = frac(i.uv * num);
    // sinカーブでアニメーション
    return box(repeat, abs(sin(_Time.y * 3)));
}
結果

f:id:soramamenatan:20200607145718g:plain

アニメーションさせることが出来たのですが、このままですと全ての四角形が同じ動きをしてしまっています。
これを以下のようにアニメーションにさせるためにどうするのかを説明します。

個別にアニメーション

f:id:soramamenatan:20200607154104g:plain


個別に制御

floor関数

各四角形を制御するためには、floor関数を使用することで表現することが出来ます。
floor関数は、

// 小数値の整数部分を返す
floor(x);

// 1が返ってくる
floor(1.5);
// 0が返ってくる
floor(0.1);

となっています。
このfloor関数によってどのように各四角形を制御しているのかの説明に移ります。

ソースコード
fixed4 frag (v2f i) : SV_Target {
    float num = 2;
    float2 repFrac = frac(i.uv * num);
    float2 repFloor = floor(i.uv * num);
    return float4(repFrac.x, repFrac.y, 0, 1);
}

repFloor変数を使用していないのは意図的です。

結果

f:id:soramamenatan:20200607151701p:plain

上記の結果になることは、frac関数での繰り返しですでに学んでいます。
そして、各uv値は以下のようになることもfrac関数で学びました。

各uv値

f:id:soramamenatan:20200607151829p:plain

ではfloor関数で何が出来るのかというと各四角形でのxとyの値を取得することができます。
現在uv値は0.0~2.0となっています。
これをfloor関数の引数としていれると整数部分のみ取得するので、0と1と2となります。 これにより各四角形でのxとyの値が取れます。

floorのイメージ

f:id:soramamenatan:20200607152339p:plain


アニメーションの組み合わせ

f:id:soramamenatan:20200607154104g:plain

こちらのgifのようにアニメーションさせるためには、素材を2つ作る必要があります。
1つは先程制作したbox関数です。

f:id:soramamenatan:20200607145310p:plain

もう1つは、下記のgifのブロックでアニメーションしているようなものになります。

f:id:soramamenatan:20200607155613g:plain

このブロックアニメーションの明るさをbox関数のサイズとして使用します。


ブロックアニメーション

box関数はすでに制作済ですので、ブロックアニメーションを制作します。
まずは、中心座標からsinカーブさせます。

ソースコード
float wave (float2 uv) {
    // (0.5, 0.5)を中心座標に
    float dist = distance(0.5, uv);
    return(1 + sin(dist * 3 - _Time.y * 3)) * 0.5;
}

fixed4 frag (v2f i) : SV_Target {
    return wave(i.uv);
}
グラフ

f:id:soramamenatan:20200607160835p:plain

結果

f:id:soramamenatan:20200607161130g:plain

次に、今滑らかにアニメーションしているものをブロック状にします。
ブロック状にする理由は、アニメーションの明るさを四角形のサイズとして使用するため、滑らかだと四角形のサイズがおかしくなってしまいます。

滑らかなアニメーションでの四角形

f:id:soramamenatan:20200607161714g:plain

ブロック状にするには、格子状に区切りその中心点の色を格子で出来た四角形の色としてあげれば良いです。

イメージ

f:id:soramamenatan:20200607162949p:plain

ソースコードに起こしていきます。

ソースコード
float wave (float2 uv, float num) {
    // 格子状で区切った座標の中心を自身のuvとする
    uv = (floor(uv * num) + 0.5) / num;
    // (0.5, 0.5)を中心座標に
    float dist = distance(0.5, uv);
    return(1 + sin(dist * 3 - _Time.y * 3)) * 0.5;
}

fixed4 frag (v2f i) : SV_Target {
    return wave(i.uv, num);
}
結果f:id:soramamenatan:20200607155613g:plain

最後に組み合わせます。

ソースコード
// 四角形を描画する
float box (float2 uv, float size) {
    size = 0.5 + size * 0.5;
    uv = step(uv, size) * step(1.0 - uv, size);
    return uv.x * uv.y;
}

// ブロック状の波アニメーション
float wave (float2 uv, float num) {
    // 格子状で区切った座標の中心を自身のuvとする
    uv = (floor(uv * num) + 0.5) / num;
    // (0.5, 0.5)を中心座標に
    float dist = distance(0.5, uv);
    return(1 + sin(dist * 3 - _Time.y * 3)) * 0.5;
}

fixed4 frag (v2f i) : SV_Target {
    float num = 10;
    float2 repFrac = frac(i.uv * num);
    float size = wave(i.uv, num);
    return box(repFrac, size);
}
結果

f:id:soramamenatan:20200607154104g:plain

これで各四角形を個別にアニメーションさせることができました。


他のアニメーション

色をつける

ソースコード
// ブロックの波アニメーション
float boxWave (float2 uv, float num) {
    float2 repFrac = frac(uv * num);
    float size = wave(uv, num);
    return box(repFrac, size);
}

fixed4 frag (v2f i) : SV_Target {
    return float4(boxWave(i.uv, 9),
                    boxWave(i.uv, 18),
                    boxWave(i.uv, 36),
                    1);
}
結果

f:id:soramamenatan:20200607164437g:plain


疑似乱数

ソースコード
// ブロックの疑似乱数
float boxRand (float2 uv, float num) {
    // 格子状で区切った座標の中心を自身のuvとする
    uv = (floor(uv * num) + 0.5) / num;
    // 疑似乱数
    float offset = frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453) * 5;
    return(1 + sin( _Time.y * 3 + offset)) * 0.5;
}

fixed4 frag (v2f i) : SV_Target {
    float num = 10;
    float2 repFrac = frac(i.uv * num);
    float size = boxRand(i.uv, num);
    return box(repFrac, size);
}
結果

f:id:soramamenatan:20200607165323g:plain

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