🧐

Unityシェーダー超入門⑤ 円を描画する

2023/04/16に公開

今回は図形を描画してみようと思います。

まず、下記の画像のようなシンプルな円を描こうと思います。

下記のコードで描いています。

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

        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
            {

                float4 col = 0;
                float2 uv = i.uv;
                float2 gv = uv-.5;
                float circle = smoothstep(.1,.1,length(gv));
                col += circle;
                return col;
            }
            ENDCG
        }
    }
}

円を各処理はお察しの通りfragシェーダーで完結しています。

03_SmoothShader
            fixed4 frag (v2f i) : SV_Target
            {

                float4 col = 0;
                float2 uv = i.uv;
                float2 gv = uv-.5;
                float circle = smoothstep(.1,.1,length(gv));
                col += circle;
                return col;
            }

では、解説を始めます。
まず、UVを用いて描画をすることとなります。
float2 uv = i.uv;でuvを宣言しておきます。
このままuvを使用してもいいのですが、ひと手間加えましょう。
float2 gv = uv-.5;でuvから0.5を引きましょう。
ちなみに.5は0.5と同位です。他の解説でもたまに出てくるので覚えておきましょう。

何故UVを-0.5するのか

結論から述べると計算が楽になるからです。
中心が(0,0)の方が描画計算がシンプルになるからです。
もともとのUV座標系は、(0, 0) から (1, 1) の範囲です。
僕は前回、中心にしたいなら0.5足すのでもいいのでは…とか思ってたのですが
単純に0.5足しちゃうと (0.5, 0.5) から (1.5, 1.5) になって中心が(1, 1)になっちゃいますね。
冷静に考えれば-0.5するのは至極同然です。中心を(0,0)にしたいだけです!

smoothstep関数とは

補間関数です、前にグラデーションを作った際に使ったlerpも同じ補間関数です。
あまり突っ込んだ解説をしすぎても返って解りにくいので、lerpに比べて補間のされ方が滑らかになります。lerpの補間のされ方は一定の補間がなされるので直線的です。
図に表すと分かりやすいと思います。

滑らかな輪郭を作りたい。今回だと円形を作りたい時には便利な関数ということが分かりますね。
では、より具体的に説明をしていきます。

smoothstep(.1,.1,length(gv));
ここをまずsmoothstep(a,b,c)と考えましょう。
まず、引数の説明になります。
第1引数aは下限値
第2引数bは上限値
第3引数cは補間対象となる値

そしてこの関数は以下のような挙動となります。
1.cがa以下の時、戻り値0が返される。
2.cがb以上の時、戻り値1が返される。
3.cがaとbの間の値の場合、戻り値は0から1の間で返される。
  上の図を見ると滑らかに返されるのもよく分かると思います。

第三引数にあるlength関数はベクトルの大きさ(長さ)を計算する関数となっています。
ここで注目して欲しいのはgvが引数に使われているということです。
gvはuvから0.5をして(0,0)が中心となっている座標の変数です。
つまり、gvを原点とした、各ピクセル(テクスチャ)座標のベクトルをlengthで計算し
戻ってきた値を評価をすることで色の塗り分けをしているということです。
lengthから戻ってきた値が0.49であれば1で、0.11であれば0になります。
先程説明した通り、補間のなされ方は滑らかになるめカーブが描かれます。
なので円形になるわけです。同時にエッジが効いているのは第1引数、第2引数が同じ値になると
smoothstepの線形補間が滑らかなカーブから直線的になります。
例えば以下のように第2引数を変えると変化があります

03_SmoothShader
            fixed4 frag (v2f i) : SV_Target
            {

                float4 col = 0;
                float2 uv = i.uv;
                float2 gv = uv-.5;
                float circle = smoothstep(.1,.12,length(gv)); //第二引数を.12
                col += circle;
                return col;
            }

別の視点からでも見てみましょう。

03_SmoothShader
            fixed4 frag (v2f i) : SV_Target
            {

                float4 col = 0;
                float2 uv = i.uv;
                float2 gv = uv-.5;
                float circle = smoothstep(.1,.1,length(gv.x)); //gv.xに変更
                col += circle;
                return col;
            }


シンプルな線が出てきました。
そして、smoothstep関数を使って何故線が出来たのかの解説は以下の画像のようになります。
上記で記載した、smoothstep(a,b,c)と考え、下記画像と照らし合わせながら考えてみましょう。

こう考えると分かりやすいかもです。自分で作った図になるので分かりにくかったら申し訳ない…!
smoothstepはよく使うのでしっかりと物にしていきたいですね…!
それと、円を描く手段はこの方法以外にも沢山あります!それらもいず書けたらなと思います。
それでは、今回はここまでとなります。

Discussion