【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 はテクスチャ座標を表しているので、その値をそのまま色に当てはめている。

【Unity-Shader】#01 オブジェクトを単色で塗りつぶす

フラグメントシェーダーを使ってオブジェクトを単色で塗りつぶします。

f:id:snoopopo:20200824102131p:plain

全文

Shader "Custom/Color"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

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

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(1, 0, 0, 1);
            }

            ENDCG
        }
    }
}

頂点シェーダ

おさらい:頂点シェーダーは頂点の数分よばれる。

           struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

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

UnityObjectToClipPos

引数で受け取った頂点の座標=3D座標 をビュー座標(画面に映る座標)に変換してくれる便利関数。

インクロードしている UnityCG.cginc に定義されている。

セマンティクス POSITION、SV_POSITION

セマンティクスとは この値がどういう意味を表すのかを定義したもの。
この定義をみてレンダリングパイプラインもこの値がなんなのか判断できる。

ここで出てきているPOSITIONSV_POSITIONはどちらも座標位置を表す。
頂点シェーダの出力であるレンダリングパイプラインに渡す座標は、SV_POSITION として、POSITIONと区別するために利用されているっぽい。

フラグメントシェーダー

おさらい:フラグメントシェーダは画素数分よばれる。

           fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(1, 0, 0, 1);
            }

今回は赤く塗りつぶすだけなので、引数でつかった値を使わず、そのままその画素の色として (1,0,0,1)=赤 を返している。

fixed4 型

4つの固定小数点で構成される型。よく色を表すのに使ったりする。

【Unity-Shader】#00 レンダリングパイプライン

この記事は…

booth.pm

主に上記の電子書籍で学んだことを自分用にまとめたものです。
Shaderを学ぶのに始めに読むのにかなりお勧めの本でした!

UnlitShader

Un Lighting Shader の略。
陰影や輝きを表現する「ライティング」「シャーディング」といった処理を含まないシェーダのこと。
オブジェクトの設定された色やテクスチャがそのまま描画されるもっともシンプルなシェーダのこと。

つまり、学習しやすい。

3Dオブジェクトを描画するには

「レンダリングパイプライン」と呼ばれる工程を行うことで、3Dオブジェクトが画面に出力される。

レンダリングパイプライン

1:頂点の映る位置を算出する ⇒Vertex Shader
2:ポリゴンの表裏を確認する ⇒Culling
3:描画する画素を決定する ⇒Rasterize
4:ZBufferの値を比較する ⇒Z Test
5:画素を描画する ⇒Fragmet Shader(Pixel Shader)
6:描画する色を合成する ⇒Blending
7:ZBufferの値を更新する ⇒Z Write

1と5、つまりVertex ShaderとFragment Shader の処理部分はプログラムを実装して自由に変更することができる。

プログラマブルシェーダー

実装するプログラムによってオブジェクトの描画方法を任意に実装する仕組みのこと。
フラグメントシェーダや、頂点シェーダはまさにこれ。

1:頂点の映る位置を算出する Vertex Shader

・オブジェクト←複数のメッシュ
・メッシュ←複数のポリゴン
・ポリゴン←3つの頂点

画面上のどの位置に頂点を描画するのか算出する。ポリゴン1つを表示するには3つ頂点があるので3回行う。

2:ポリゴンの表裏を確認する

ポリゴンには表裏が設定されている。
 →unityではその頂点の番号が時計まわりに見える方向を表として定義している

描画負荷を下げるために裏面をむいているポリゴンは描画しないようにしている=culling (カリング)
shaderにおけるcullingを Backface Culling という

というのがデフォルトの動作で設定によって、裏面のみ描画、表・裏どっちも描画、という風にもできる

3:描画する画素を決定する

ポリゴンが画面上のどの画素に描画されるかを決定する=ラスタライズ Rasterize
1で頂点位置が決まり、3でポリゴンがどの画素で描画するかが決まる

4:ZBufferの値を比較する

描画する画素に既に描画されているポリゴンがないか確認する。描画済みのポリゴンが存在するかどうかは「Z Buffer」に書き込まれている
「Z Buffer」は描画する画面と同じ解像度を持った異なる画面
「Z Buffer」=「Depth Buffer」・深度バッファ
「Z Buffer」にはカメラからポリゴンまでの距離を表す値=z座標が書き込まれている

描画しようとしているポリゴンのZ値がすでに描画されているポリゴンのZ値よりも小さい場合は、
描画しようとしているポリゴンの方が手前になる

Z値を比較する処理を「Z Test」、またはDepth Testという
「Z Testに成功する」とは、Z値を比較して、新しいポリゴンが描画できる状態のことをいう

5:画素を描画する Fragmet Shader(Pixel Shader)

画素に描画する色を算出して描画する
色は光源や、他のオブジェクトの影を考慮したり、あるいは、テクスチャを参照するなどして算出する

この処理はラスタライズで決定された画素の数分行われる。

6:描画する色を合成する Blending

半透明なオブジェクトを描画する場合、重なった奥にあるオブジェクトを描画してから、そのあとに半透明なオブジェクトの描画を行う。
半透明なので、先に描画したオブジェクトの色と合成して描画する。

★不透明なオブジェクト→半透明なオブジェクト という順番で描画する

7:ZBufferの値を更新する

画素の描画が終わったら、Z Bufferに新しいZ値を書き込んで更新する。=「Z Write」