【Unity-Shader】#07 直線を描く

今回は、線をフラグメントシェーダで描画します。「スキャンライン」というらしい。

線を描く

f:id:snoopopo:20200904083731p:plain

fixed4 frag(v2f i) : SV_Target
{
    return step(0.3, i.uv.y) * step(i.uv.y, 0.4);
}

step

step(閾値, x)

xが閾値以上なら1, 閾値未満なら0 を返す。


上記の例は、UV座標のY座標が0.3~0.4 の位置にあるときに1を返すので、1=白が表示されている。

sin波を使って複数線を描く

fixed4 frag(v2f i) : SV_Target
{
    return sin(i.uv.y * 50);
}

複数線を書くために 0→1→0→1→0・・・ となる値が必要なので、今回はsin波を使います。

f:id:snoopopo:20200904092449p:plain

sin でウェーブさせた値をそのまま色として出力すると、上記のようになり、0(黒)→1(白)→0(黒)→1(白)→0(黒)・・・ を繰り返していることがわかる。

sinでだした値はゆるやかに0から1をいききした値になっている 0→0.1→0.2→…→0.9→0.8…→1
のでぼやけた線みたいな表示になっている。

ここでstepを使い、閾値を元に0か1だけの値にしてはっきりした直線にする。

f:id:snoopopo:20200904093414p:plain

fixed4 frag(v2f i) : SV_Target
{
    return step(0.3, sin(i.uv.y * 50));
}

線を動かす

fixed4 frag(v2f i) : SV_Target
{
    return step(0.3, sin((i.uv.y - _Time.y * 0.2) * 50));
}

_Timeを使ってUV座標のyを下に時間経過で移動させて線が動いているようにみせる。

f:id:snoopopo:20200904094136g:plain

画像と組み合わせたり…

f:id:snoopopo:20200904095920g:plain

白と黒の線が引けたところで、画像の表示と組み合わせてみるとちょっとゲームで使えそうになってきました。

fixed4 frag(v2f i) : SV_Target
{
    fixed4 color = tex2D(_MainTex, i.uv);
    if (color.a <= 0) {
        discard;
    }

    return (1 - step(0.8, sin((i.uv.y - _Time.y * 0.2) * 50))) * color;
}

画像の色と乗算しているため、 黒(0)は黒いまま、白(1)はx1となるので画像の色がそのまま表示されます。


f:id:snoopopo:20200904100915g:plain

fixed4 frag(v2f i) : SV_Target
{
    fixed4 color = tex2D(_MainTex, i.uv);
    color *= (1 - step(0.8, sin((i.uv.y - _Time.y * 0.2) * 50)));
    if (color.a <= 0) {
        discard;
    }

    return color;
}

黒い線の部分は破棄 discard しちゃうのもよさげだ…

【Unity-Shader】#06 テクスチャの色を徐々に変える

テクスチャの色を徐々に変える f:id:snoopopo:20200903100912g:plain

▼ 前回の記事 www.snoopopo.com

全文

Shader "Custom/GrayScaleColor"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _Volume("Volume", Range(0,1)) = 0
        _Color("Color", Color) = (1, 1, 1)
    }

    SubShader
    {
        Cull Off
        AlphaToMask On

        Tags
        {
            "Queue" = "AlphaTest"
            "RenderType" = "AlphaTest"
        }

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            sampler2D _MainTex;
            float _Volume;
            float4 _Color;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);

                fixed gray = dot(color.rgb, fixed3(0.299, 0.587, 0.114));
                fixed4 targetColor = _Color * gray;

                fixed4 viewColor = lerp(color, targetColor, _Volume);
                return fixed4(viewColor.r, viewColor.g, viewColor.b, tex2D(_MainTex, i.uv).a);
            }

            ENDCG
        }
    }
}

一度グレースケール化させてから色を塗る

               fixed gray = dot(color.rgb, fixed3(0.299, 0.587, 0.114));
                fixed4 targetColor = _Color * gray;

上記のソースの通り、一度グレースケール化してから色を乗算しています。

www.snoopopo.com

こうすることによって、もともとの画像についている色味が消えてちょっとメタリック?なかんじになります。

(左)グレースケール化せずに乗算 (右)グレースケール化後、乗算 f:id:snoopopo:20200903102035p:plainf:id:snoopopo:20200903102042p:plain

lerp

               fixed4 viewColor = lerp(color, targetColor, _Volume);
                return fixed4(viewColor.r, viewColor.g, viewColor.b, tex2D(_MainTex, i.uv).a);

徐々に色を変えるには線形補完が使える。

【Unity-Shader】#05 グレースケール化

今回はフラグメントシェーダを使って画像をグレースケール化します。 f:id:snoopopo:20200902095717p:plain

▼ 前回の記事

www.snoopopo.com

全文

Shader "Custom/GrayScale"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Cull Off
        AlphaToMask On

        Tags
        {
            "Queue" = "AlphaTest"
            "RenderType" = "AlphaTest"
        }

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            sampler2D _MainTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                //ここにグレースケール化するコードを書く。
            }

            ENDCG
        }
    }
}

グレースケール化する方法

RGB それぞれに0.333をかけて足した値にする

単純平均法ってやつ。1を3(RGB)で割ったものを元の色に掛けることでグレースケール化できる。

           fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);

                color *= 0.333;
                fixed gray = color.r + color.g + color.b;

                return fixed4(gray, gray, gray, tex2D(_MainTex, i.uv).a);
            }

f:id:snoopopo:20200902093423p:plain

NTSC 加重平均法

fixed3(0.299, 0.587, 0.114) の数字、どっから出てきた?! って感じだが、
これは人間の目に合わせてRGBの値をこの数字の割合でみるとちょうどグレースケール化される値として割り出されているものらしい。。。

           fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);

                fixed gray = dot(color.rgb, fixed3(0.299, 0.587, 0.114));

                return fixed4(gray, gray, gray, tex2D(_MainTex, i.uv).a);
            }

f:id:snoopopo:20200902094021p:plain

【Unity-Shader】#04 部分的に透明なテクスチャを表示する

前回までは不透明なテクスチャを使っていたので、今回からは部分的に透明なテクスチャを表示します。

f:id:snoopopo:20200827133540p:plain

▼ 前回の記事

www.snoopopo.com

全文

Shader "Custom/Transparent"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Tags
        {
            "Queue" = "AlphaTest"
            "RenderType" = "AlphaTest"
        }

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            sampler2D _MainTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);

                if (color.a <= 0) {
                    discard;    //画素を塗りつぶさない。ZBufferの更新も行わない
                }

                return color;
            }

            ENDCG
        }
    }
}

フラグメントシェーダー

           fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);

                if (color.a <= 0) {
                    discard;    //画素を塗りつぶさない。ZBufferの更新も行わない
                }

                return color;
            }

画像の中で透明な画素は当然、tex2Dで取得した色のアルファ値が0になっているはずなので、 0だったら、何もしないようにする。

discard

画素を塗りつぶさない。ZBufferの更新も行われないので、奥に重なったオブジェクトがある場合は、
そのオブジェクトの色がそのまま描画される。

clip

if文をかかずにclipを使うこともできる。

           fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);
                clip(color.a - 0.1);
                return color;
            }

引数の値が0未満の場合に discard と同じように塗りつぶしを行わない。

【Unity-Shader】#03 テクスチャを半透明に表示する

前回張ったテクスチャを半透明に表示します。

f:id:snoopopo:20200827122949p:plain

▼ 前回の記事

www.snoopopo.com

全文

Shader "Custom/Transparent"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        //半透明が適応されるために必要
        Tags
        {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }

        Blend SrcAlpha OneMinusSrcAlpha //重なったオブジェクトの画素の色とのブレンド方法の指定

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            sampler2D _MainTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);
                color.a = 0.5; //半透明にする
                return color;
            }

            ENDCG
        }
    }
}

フラグメントシェーダー

           fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);
                color.a = 0.5; //半透明にする
                return color;
            }

おさらい:フラグメントシェーダーはその画素の色を決定することが役割。

単純にtex2Dを使ってこの画素に対する画像の位置の色を取得し、そのアルファ値をシェーダーで変えている。

これだけで半透明に表示されそうだと思ったが、半透明にならない。

アルファ値を反映させるには?

       //半透明が適応されるために必要
        Tags
        {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }

        Blend SrcAlpha OneMinusSrcAlpha //重なったオブジェクトの画素の色とのブレンド方法の指定

上記のような記述が必要になる。

Tags

レンダリングパイプラインに、こういう設定で処理を行ってね、というのを設定できる。

unity固有の構文=ShaderLab で記述方法が決まっている。

docs.unity3d.com

半透明なオブジェクトは不透明なオブジェクトの後に描画しないといけない理由

半透明のオブジェクトを描画したい場合、先に不透明なオブジェクトを描画しておく必要がある。
手前にある半透明のオブジェクトと奥にある不透明のオブジェクトが重なったときに、
先に手前にある半透明のオブジェクトを描画してしまうと問題がおきるためである。

なぜなら、レンダリングパイプラインの手順上、
1:手前にある半透明のオブジェクトを描画 →Zbufferに半透明の色が入る
2:奥にある不透明のオブジェクトを描画する際にZTestで奥にあるので、重なった部分の色は描画しなくなる
⇒重なったときに奥にある不透明なオブジェクトも見えていないとおかしいが、描画されないことになってしまう。

このあたりの話は以下の記事で書いてた: www.snoopopo.com

そのため、Queue を使い、必ず不透明なオブジェクトの後に透明なオブジェクトを描画するようにする。

そうすると・・・
1:奥にある不透明のオブジェクトを描画する →Zbufferに不透明な色が入る
2:手前にある半透明のオブジェクトを描画する →Zbufferに既に入っている色と自分の色をブレンドして表示する

という手順にすることができる。

f:id:snoopopo:20200827125605p:plain

TagsのQueue

というわけでオブジェクトの描画順を意識しないといけないことがわかったわけだが、
その指定をするのが Queue になる。

| 指定できる文字列 | 説明 | 値 | | Geometry | 不透明なオブジェクト用。Queueを設定していないとデフォルトでこの指定になっている| 2000 | | Transparent | 半透明なオブジェクト用。| 3000 |

Queueは文字列で指定しているが、内部的には数値になっており、数値が小さい方から先に描画される。

ブランド方法の指定

       Blend SrcAlpha OneMinusSrcAlpha //重なったオブジェクトの画素の色とのブレンド方法の指定

半透明なオブジェクトの場合は、奥にある重なったオブジェクトの色と合成して表示すると自然な表示になる。

その色の合成方法の指定が上記。

【Unity-Shader】#02 不透明なテクスチャを表示する

不透明な画像=テクスチャを表示するシェーダーを作成します。 f:id:snoopopo:20200825132847p:plain

全文

Shader "Custom/Texture"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;   //テクスチャ座標
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            sampler2D _MainTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);
                return color;
            }

            ENDCG
        }
    }
}

Properties

properties に設定するとマテリアルのインスペクタから値を設定することができる。

   Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

_MainTex はシェーダー内の変数 sampler2D _MainTex を表していて、マテリアルで設定した値はこの変数に入ることを表す。

   SubShader
    {
        Pass
        {
            CGPROGRAM

            sampler2D _MainTex;

            ENDCG
        }
    }


"Textue" →マテリアルのインスペクタ に表示する名称。
2D →2D型 であることを表す。テクスチャを設定したい場合には2D型を使う。
"white" →テクスチャが設定されていないときのデフォルト色。

sampler2D 型

テクスチャの参照そのものが代入される型。 後術するtex2Dメソッドとセットで使われる。

頂点シェーダ

           struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;   //テクスチャ座標
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            sampler2D _MainTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

UV座標

ポリゴンの各頂点にはテクスチャの座標情報が含まれている。
テクスチャの座標情報とは、張り付けられるテクスチャのどの画素がその頂点の位置に対応するかを表す情報。
これをUV座標といい、左下が(0,0)で、右上が(1,1)で表される。真ん中は(0.5,0.5) になるという意味。

この情報は、頂点が持っている情報であるため、 まずはレンダリングパイプラインから頂点シェーダにUV座標をもらい、それをフラグメントシェーダに連携する。 その後、フラグメントシェーダで、UV座標を元に画像からその画素に対する色を取得する。

セマンティクス TEXCOORD

UV座標を表す。

フラグメントシェーダ

上述した通り、インプットのv2fにはUV座標が定義されており、頂点シェーダで設定したものが入っている。

これを使い、この画素に対するテクスチャを取得する。

           fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv);
                return color;
            }

tex2D

指定されたUV座標の画素の色を返す関数。

【Unity-Shader】#0? テクスチャ座標をつかって色を算出する

フラグメントシェーダーを使ってオブジェクトの色をテクスチャ座標をつかって算出します。 f:id:snoopopo:20200824111224p:plain

全文

Shader "Custom/UvColor"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD;
            };

            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 fixed4(i.uv.x, i.uv.y, 0, 1);
            }

            ENDCG
        }
    }
}

フラグメントシェーダ

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(i.uv.x, i.uv.y, 0, 1);
            }

頂点シェーダから取得した TEXCOORD はテクスチャ座標を表しているので、その値をそのまま色に当てはめている。