Unityで学ぶ2Dシェーダー その2

はじめに

シェーダーの知識0の人が読んで分かるような資料がネット上に無さそうだったので書きました。
自分の知識もゼロなので手探りで学びながら書いています。

この記事はその1の続きです。
なお、この記事ではタイトルで書いたように2Dのことにのみ触れていきます。

今回は簡単なシェーダーアートについて書いていきます。

下準備

結果を表示するための正方形と長方形のテクスチャを用意しましょう。

長方形のテクスチャは最後の網目模様で使用します。

シェーダーサンプル

では実際にサンプル書きながら解説していきます。

時間経過で変化するグラデーション

結果

コード

Shader "Art/DynamicGradation" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct VertexInput {
                float4 pos:  POSITION;    // 3D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            struct VertexOutput {
                float4 sv_pos:    SV_POSITION; // 2D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            // 頂点シェーダー
            VertexOutput vert (VertexInput input) {
                VertexOutput output;
                output.sv_pos = UnityObjectToClipPos(input.pos);
                output.uv = input.uv;

                return output;
            }

            // ピクセルシェーダー
            float4 frag (VertexOutput output) : SV_Target {
            	float3 col = 0.5 + 0.5 * cos(_Time - output.uv.xyx + float3(0, 2, 4) );
                return float4(col, 1);
            }

            ENDCG
        }
    }
} 

解説

概要

GLSLのヤバいシェーダーが見れることで有名なShaderToy。(リンク先とても重たいです。注意)
そこでシェーダーの新規作成をしたときのコードをそのままHLSLに直したものです。

ここで前回と大きく違うのは_Timeを使っていることです。
これはビルトイン変数で、特に準備することなく使えます。ほかの変数はUnityのドキュメントに記載されています。

Unity は、シェーダー用のビルトイン・グローバル変数をいくつか提供しています。例えば、現在のオブジェクトの変形マトリックス、ライトパラメーター、現在のタイムなどです。これらは他の変数と同様、 シェーダープログラム で使用します。ただ一つの違いは、宣言する必要がないことです。これらの変数はすべて、自動的に含められるイ...

GLSLとHLSL

シェーダー言語はいくつかありますが、その中でもとくに有名なのがGLSLとHLSLです。
ざっくり言うとGLSLはOpenGL向けのもので、HLSLはDirectX向けのもの。

両者はとても似ており、書き換えがしやすいです。
類似点や相違点についてはいくつも記事があるので省略します。

結果


※円以外が黒なのは背景色で、この部分のテクスチャは透明です。

コード

Shader "Art/Circle" {
    Properties {
        _Radius ("Radius", Range(0, 1)) = 1
        _Width ("Width", Range(0, 1)) = 0.1
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct VertexInput {
                float4 pos:  POSITION;    // 3D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            struct VertexOutput {
                float4 sv_pos:    SV_POSITION; // 2D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            // 頂点シェーダー
            VertexOutput vert (VertexInput input) {
                VertexOutput output;
                output.sv_pos = UnityObjectToClipPos(input.pos);
                output.uv = input.uv;

                return output;
            }

            float _Radius;
            float _Width;
            float4 _Color;

            // ピクセルシェーダー
            float4 frag (VertexOutput output) : SV_Target {
            	float2 pos = float2( 2*(output.uv.x-0.5), 2*(output.uv.y-0.5));
            	float len = length(pos);
            	if (len > _Radius || len < (_Radius-_Width))
           			discard;
                return _Color;
            }

            ENDCG
        }
    }
} 

解説

概要

円模様を描画するシンプルなシェーダーです。
特徴として、UnityのInspectorから値をいじることで形状変化するようにしています。

なお、Rangeなどのプロパティは前回の記事で触れています。

座標の正規化

その1で触れたように、
HLSLにおける座標系は左下が(0,0)で右上が(1,1)です。

円を描画するにあたって、中心からの距離で描画するか判定したいため、
以下のようにすることで座標系を画像の中央が(0,0)になるよう正規化します。

float2 pos = float2( 2*(output.uv.x-0.5), 2*(output.uv.y-0.5));

描画の判定

float4 frag (VertexOutput output) : SV_Target {
    float2 pos = float2( 2*(output.uv.x-0.5), 2*(output.uv.y-0.5));
    float len = length(pos);
    if (len > _Radius || len < (_Radius-_Width))
        discard;
    return _Color;
}

まず組み込み関数length()で現在のピクセルにおける中心からの距離を計算します。
組み込み関数にはいろいろ便利なものがあるので、ざっくり把握しておくといいかもしれません。
https://msdn.microsoft.com/ja-jp/library/bb509611(v=vs.85).aspx

そして、中心からの距離を見て、円の幅を考慮した半径距離であれば描画する、という流れです。
描画しないとき、discard;とすることで結果を出力せず、ピクセルシェーダーを終わらせます。
結果の項にある画像にて円がギザギザして滑らかでないのは、これにより半透明で出力する部分がないためです。

Do not output the result of the current pixel.

discard;されなかったとき、つまり描画するときプロパティで指定された色の値を出力します。

波紋

結果

コード

Shader "Art/Ripple" {
    Properties {
        _Speed ("Speed", Range(-10, 10)) = 1 
        _Color ("Color", Color) = (0.3, 0.6, 0.7, 1)
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct VertexInput {
                float4 pos:  POSITION;    // 3D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            struct VertexOutput {
                float4 sv_pos:    SV_POSITION; // 2D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            // 頂点シェーダー
            VertexOutput vert (VertexInput input) {
                VertexOutput output;
                output.sv_pos = UnityObjectToClipPos(input.pos);
                output.uv = input.uv;

                return output;
            }

            int _Speed;
            float4 _Color;

            // ピクセルシェーダー
            float4 frag (VertexOutput output) : SV_Target {
            	float2 pos = float2( 2*(output.uv.x-0.5), 2*(output.uv.y-0.5));
            	float circle = sin(length(pos) * 70.0 - _Time * 90 * _Speed);
                return float4(1-circle*(1-_Color.r), 1-circle*(1-_Color.g), 1-circle*(1-_Color.b), _Color.a);
            }

            ENDCG
        }
    }
} 

解説

概要

波紋のように時間経過で円が動くシェーダーです。

特に新しいことはありませんが、ここまでのまとめのようなシェーダーです。
座標を正規化し、中心からの距離でsin()をとることで色を決定します。

なお、returnの際それぞれ1-**としているのは色の調整です。
このシェーダーもInspectorから操作することができます。

網目模様

結果

コード

Shader "Art/Knitting" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct VertexInput {
                float4 pos:  POSITION;    // 3D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            struct VertexOutput {
                float4 sv_pos:    SV_POSITION; // 2D座標
                float2 uv:   TEXCOORD0;   // テクスチャ座標
            };

            // 頂点シェーダー
            VertexOutput vert (VertexInput input) {
                VertexOutput output;
                output.sv_pos = UnityObjectToClipPos(input.pos);
                output.uv = input.uv;

                return output;
            }

            // ピクセルシェーダー
            float4 frag (VertexOutput output) : SV_Target {
            	float2 pixel_pos = float2(output.uv.x * _ScreenParams.x, output.uv.y * _ScreenParams.y);
                return float4(1 - sin(pixel_pos.x/5) * 0.7, 1, 1 - sin(pixel_pos.y/5) * 0.7, 1);
            }

            ENDCG
        }
    }
} 

解説

概要

網目模様のシェーダーです。
この回では下準備で用意した長方形のテクスチャを使いましょう。

処理の流れはいたって単純で、現在の座標に対してsin()をとり、網目模様を描画しています。

絶対座標

さて、今まで正方形のテクスチャで作業を行ってきました。
それはHLSLにおける座標系がタテもヨコも実際の比率にかかわらず0~1で、正方形でないと歪んでしまうからです。

ということで今回は歪むことの対処法です。
といってもこれも簡単で、現在のピクセルの絶対座標は_ScreenParamsで与えられるため、これを掛けてあげるだけです。

float2 pixel_pos = float2(output.uv.x * _ScreenParams.x, output.uv.y * _ScreenParams.y);

さいごに

シェーダーアートはGLSLの資料ばかりでつらかったので書きました。
その3の記事が出せるよう頑張ります。