🐥

Unityシェーダー超入門⑥ グリッドの作り方

2023/04/22に公開

今回はsmoothstepを使ってグリッドを描画しようと思います。

こんな感じ

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

04_Grid
Shader "04_Grid"
{
    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
            {
                float2 uv = i.uv * 6;
                float2 gv = frac(uv);
                float edge_x = smoothstep(0, 0.02, gv.x);
                float edge_y = smoothstep(0, 0.02, gv.y);
                float value = edge_x < edge_y ? edge_x : edge_y;
                return float4(value,value,value,1.0);
            }
            ENDCG
        }
    }
}

処理そのものはfragシェーダー内で完結しています。

04_Grid
            fixed4 frag (v2f i) : SV_Target
            {
                float2 uv = i.uv * 6;
                float2 gv = frac(uv);
                float edge_x = smoothstep(0, 0.02, gv.x);
                float edge_y = smoothstep(0, 0.02, gv.y);
                float value = edge_x < edge_y ? edge_x : edge_y;
                return float4(value,value,value,1.0);
            }

とてもシンプルな構成です。
ただ、自分的には結構大事な箇所が多いのでしっかり解説出来たらと思います。

UVを分割するためには…?

とりあえずUVってなんだっけ…ってなった人はこの図を思い出してください。

float2 uv = i.uv * 6;
そしてここではUVを6倍にしています。
6倍をすると単純に面積が6倍になってこうなる…ってイメージしますよね。

イメージとしても正しいですし、「実数」もそうなっています。
その上で、今回作ろうとしているグリッド状にするには
このUVを分割して作るにはどうしたらいいでしょうか?
単純に割ればいいだけではあるのですが、frac関数を使うと楽になります。
float2 gv = frac(uv);
frac(x)はxの小数部分を返します。xが2.1なら0.1が返ってきます。
uvが6倍されてる今回のコードにおいてfracの引数uvが(5.2,5.5)だった場合、戻り値は(0.2,0.5)になります。
結果uvは6倍されているが、frac関数によりgvに入る値は0-1の範囲に制限された値の連なりとなり
以下の画像のようになるということです。
グリッドで分割されたボックスのことはセルと呼ぶとしましょう

セルは一見それぞれが個別の物のように見えますが、参照しているUV座標は等しく0-1です。
つまり、y軸の0~0.1の区間を黒く塗りつぶすと、等しく全てのセルのY軸0~0.1の区間が塗りつぶされます。そして、前回図形を作る時に使ったsmoothstep関数の出番です。

04_Grid
float edge_x = smoothstep(0, 0.02, gv.x);
float edge_y = smoothstep(0, 0.02, gv.y);

ここではピクセルの色を決めています。
言語化するとこう
smoothstep(0, 0.02, gv.x or gv.y)は
・0-0.02の範囲にgv.x or gv.yがあれば、0-1の補間された値を返します。
・gv.x or gv.yが0未満の場合は、0を返します。
・gv.x or gv.yが0.02を超える場合は、1を返します

そして、以下の条件演算でvalueにedge_xまたはedge_yのどちらかを入れているだけです。
float value = edge_x < edge_y ? edge_x : edge_y;
結果、0に近い値がvalueに入り。
xとyの始点0~終点0.02の間が黒塗られていくというわけです

ちなみに、反転させたい場合はこうすると出来ます。
smoothstepの引数と演算子の条件を調整するだけですね。

04_Grid
float edge_x = smoothstep(0.98, 1, gv.x);
float edge_y = smoothstep(0.98, 1, gv.y);
float value = edge_x > edge_y ? edge_x : edge_y;


こんな風にちょっとした改変は必ず行った方がいいです。

大事だなと感じたこと

今度から自分のメモ程度にですが、大事だと感じたことを残していきます。
改めてですがUVに限らずですが0-1で考えれるようにするのはとても大事だと思いました。
また、コードを言語化するのと同様に「こうしたら」「こうなるはず」の仮説と検証は何度もやった方がいいですね。これをやっていくうちに、今こうなってるのはこうだからが言葉や文字で表現出来るようになってきました。当てずっぽうで入力して成立する場合もあるが、理解をしていく上では微々たる経験値にしかならない。
もう一つ、大事だなと思ったのが図などに書き起こすことです。
頭で理解をし、言葉にし、図にまで起こせればかなり理解度が進んだ気がする。
では、また次回

Discussion