はじめてのシェーダーお絵描き


この記事はリンク情報システムの2020新春アドベントカレンダー Tech Connectのリレー記事です。
engineer.hanzomon のグループメンバによってリレーされます。
(リンク情報システムのFacebookはこちらから)

2020新春アドベントカレンダー Tech Connect インデックスはこちら

はじめに

明けましておめでとうございます。
というには少し遅いですね。
年末年始のんびり寝てたので白紙のページに慄いています。
とりあえずそろそろShader触ってみたいと思っていたので今回はShaderで遊ぼうと思います。

Shaderとは?

名前そのままに影(shade)を描画するためのものです。
表面の色をいじるフラグメントシェーダーや頂点をいじるバーテックスシェーダーなどいくつか種類があります。
また、HLSLやGLSLといった特殊なプログラミング言語を用いて記述します。

Unityでシェーダーを書く

とにもかくにもプロジェクトを作って準備をします。
shaderとmaterialのフォルダを作成してそれぞれにシェーダー(standard surface shader)とマテリアルを作成します。
作成したマテリアルに作成したシェーダーを設定しておきましょう。

まずは円を描きたいのでどちらも名前はcircleにしました。

円を描く

ここから円を描いていきます。
参考はこちら
参考にしたページではワールド座標を使っていますが今回はUV座標を使うように変更してみます。

struct Input {
    float2 uv_MainTex;
};

void surf (Input IN, inout SurfaceOutputStandard o) {
    float dist = distance( fixed3(0.5,0.5,0), IN.uv_MainTex );
    float radius = 0.1;
    if(  radius < dist ){
        o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1);
    } else {
        o.Albedo = fixed4(1,1,1,1);
    }
}

UV座標を使うのでInput構造体にuv_MainTexを指定します。
適当に数字を変えてみたところ、UV座標は左上が(1,1)、右下が(0,0)ぽいので中心の(0.5,0.5,0)をdistanceの第一引数にすればOKです。
ということでこんな感じに真ん中を切り取ったような感じに。

でもこれでは円を描いたという感じではないですね。
というわけで少し改良。

void surf (Input IN, inout SurfaceOutputStandard o) {
    float dist = distance( fixed3(0.5,0.5,0), IN.uv_MainTex );
    float radius_in = 0.1;
    float radius_out = 0.11;
    if(  radius_out < dist || radius_in > dist ){
        o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1);
    } else {
        o.Albedo = fixed4(1,1,1,1);
    }
}

すこしずらして内側も塗ってしまえばいい感じに円ぽくなります。
色は逆にしたほうがよかった気がしてきました。

ところでシェーダーはif文と相性が良くないらしいです。
なにやら並列処理がどうのこうので。
詳しいことはわかりませんがそういうわけでif文を使わずに円を描いてみます。

void surf (Input IN, inout SurfaceOutputStandard o) {
    float dist = distance( fixed3(0.5,0.5,0), IN.uv_MainTex );
    float radius_in = 0.1;
    float radius_out = 0.11;
    o.Albedo = step(radius_out,dist) * fixed4(110/255.0, 87/255.0, 139/255.0, 1)
        + step(dist,radius_out) * step(radius_in, dist) * fixed4(1,1,1,1)
        + step(dist,radius_in) * fixed4(110/255.0, 87/255.0, 139/255.0, 1);
}

ここではstep関数を使っています。
step関数は第一引数が第二引数より大きければ0.0、小さければ1.0を返すので外側、円、内側に項を分けて計算しています。
もっときれいに書けそうですが・・・。

四角を描く

次は四角を描いてみます。
同様にstep関数を使って1辺ずつ描いていって最後に中を白く塗りつぶしています。

void surf (Input IN, inout SurfaceOutputStandard o) {
    float width = 0.1;
    float x = IN.uv_MainTex.x;
    float y = IN.uv_MainTex.y;
    o.Albedo = step(IN.uv_MainTex.x, width) * fixed4(110/255.0, 87/255.0, 139/255.0, 1)
        + step(y, width) * step(width, x) * fixed4(110/255.0, 87/255.0, 139/255.0, 1)
        + step(1 - width, x) * step(width, y) * fixed4(110/255.0, 87/255.0, 139/255.0, 1)
        + step(1 - width, y) * step(width, x) * step(x, 1 - width) * fixed4(110/255.0, 87/255.0, 139/255.0, 1)
        + step(y, 1 - width) * step(width, y) * step(width, x) * step(x, 1 - width) * fixed4(1, 1, 1, 1);
}

絶対もっと楽に描けると思うんですけど・・・・・・。
描画結果はこんな感じになります。

おわりに

頭から煙がでそうなので今回はここまでです。
お絵描きといいながら円と四角を描いただけでしたが・・・。
まぁでも円と四角が描ければ組み合わせで色々と描けるんじゃないでしょうか!

明日は@p3ngu1nさんです。

参考

7日間でマスターするUnityシェーダ入門
【Unity】【シェーダ】シェーダにおいてif文は悪か


リンク情報システム株式会社では一緒に働く仲間を随時募集しています!
また、お仕事のご依頼、ビジネスパートナー様も募集しております。お気軽にご連絡ください。