[Unity] Vertex and Fragment Shader Examplesのサンプルを試しつつメモ


Unityでのシェーダ勉強中です。今回は、Unityのドキュメントである「Vertex and Fragment Shader Examples」に書かれているCg/HLSLのサンプルを試しつつ、気づいた点やメモを書いていこうと思います。
(ちなみに、シェーダの基礎については「シェーダを書く準備」という記事を書いたのでそちらを参照ください)

Empty Shader

Hello Worldをやるにしても、どれが最低限の状態かを知るのは大事なことです。
幸いにして、ドキュメントに「Empty Shader」として空のシェーダプログラムの記載があります。

Shader "Custom/Empty" {
    SubShader {
        Pass {
            CGPROGRAM
            ENDCG
        }
    }
}

このシェーダを利用したマテリアルを適用すると、画面にはなにも表示されなくなります。
なにも計算していないので当然ですね。
ただ、これが最低限必要なコード、ということになります。
(ちなみに、"Custom/Empty"の部分はシェーダの名前で、スラッシュで区切ると階層構造を提供できます)

Simple Shader

次に紹介されているのはシンプルなシェーダです。

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

            #pragma vertex vert
            #pragma fragment frag

            float4 vert(float4 v:POSITION) : SV_POSITION {
                return mul (UNITY_MATRIX_MVP, v);
            }

            fixed4 frag() : COLOR {
                return fixed4(1.0,0.0,0.0,1.0);
            }

            ENDCG
        }
    }
}

座標をMVP行列によって変換した後、全ピクセルを赤一色に塗っているだけのシェーダです。
実際に適用すると以下のようになります。

Simple Shader sample
(Unity documentationから引用)

Window Coordinates

今度は、ウィンドウの座標に応じて変化するシェーダです。

Shader "Custom/WindowCoordinates/Base" {
    SubShader {
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            #include "UnityCG.cginc"

            float4 vert(appdata_base v) : POSITION {
                return mul (UNITY_MATRIX_MVP, v.vertex);
            }

            fixed4 frag(float4 sp:WPOS) : COLOR {
                return fixed4(sp.xy/_ScreenParams.xy,0.0,1.0);
            }

            ENDCG
        }
    }
}

実際に適用したのがこちら。

Window Coordinates sample

このシェーダの面白いところは、ウィンドウの座標に応じて色を出力しているため、オブジェクトを移動すると色が変化する点です。
実際のコンテンツでは利用するシーンはほぼないと思いますが、どういう組み込みの値があるのかを示すいい例でしょう。

Behind bars

Shader "Custom/WindowCoordinates/Bars" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct vertOut {
                float4 pos:SV_POSITION;
                float4 scrPos;
            };

            vertOut vert(appdata_base v) {
                vertOut o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.scrPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 frag(vertOut i) : COLOR0 {
                float2 wcoord = (i.scrPos.xy/i.scrPos.w);
                fixed4 color;

                if (fmod(20.0*wcoord.x,2.0)<1.0) {
                    color = fixed4(wcoord.xy,0.0,1.0);
                } else {
                    color = fixed4(0.3,0.3,0.3,1.0);
                }
                return color;
            }

            ENDCG
        }
    }
}

今度は、ウィンドウの座標に応じて色を塗りつつ、特定の場所の場合は色をグレーにする、というものです。
タイトルの通り「バーに隠れている」ように見えるシェーダです。

上記サンプルで気になる点はComputeScreenPosでしょう。
これはUnityCG.cgincファイルで定義されている関数で、引数で渡した座標位置をウィンドウの座標位置に変換してくれるもののようです。

Behind bars sample

バーの位置はウィンドウ座標に対応しているので、オブジェクトを移動するとあたかもバーがそこにあるかのように見えます。

Vignetting

Shader "Custom/WindowCoordinates/Vignetting" {
    SubShader {
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            #include "UnityCG.cginc"

            float4 vert(appdata_base v) : POSITION {
                return mul (UNITY_MATRIX_MVP, v.vertex);
            }

            float4 frag(float4 sp:WPOS) : COLOR {
                float2 wcoord = sp.xy/_ScreenParams.xy;
                float vig = clamp(3.0*length(wcoord-0.5),0.0,1.0);
                return lerp (float4(wcoord,0.0,1.0),float4(0.3,0.3,0.3,1.0),vig);
            }
            ENDCG
        }
    }
}

Vignetting sample

leap関数は、いわゆる補間関数で、Cgの標準関数です。(interpolation)

Circles Mask

Shader "Custom/WindowCoordinates/CirclesMask" {
    Properties {
        _CirclesX ("Circles in X", Float) = 20
        _CirclesY ("Circles in Y", Float) = 10
        _Fade ("Fade", Range (0.1,1.0)) = 0.5
    }
    SubShader {
        Pass {

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            #include "UnityCG.cginc"

            uniform float _CirclesX;
            uniform float _CirclesY;
            uniform float _Fade;

            float4 vert(appdata_base v) : POSITION {
                return mul (UNITY_MATRIX_MVP, v.vertex);
            }

            float4 frag(float4 sp:WPOS) : COLOR {
                float2 wcoord = sp.xy/_ScreenParams.xy;
                float4 color;
                if (length(fmod(float2(_CirclesX*wcoord.x,_CirclesY*wcoord.y),2.0)-1.0)<_Fade) {
                    color = float4(sp.xy/_ScreenParams.xy,0.0,1.0);
                } else {
                    color = float4(0.3,0.3,0.3,1.0);
                } 
                return color;
            }
            ENDCG
        }
    }
}

Circles Mask sample

ここでのポイントはuniform修飾子ですね。
よく見ると、他のサンプルにはなかったPropertiesも追加されています。
Cg/HLSLでは、Propertiesで宣言したプロパティはuniform修飾子を付けた同名の変数を宣言することで利用できるようになります。

Texture Coordinates

Shader "Custom/TextureCoordinates/Base" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            #include "UnityCG.cginc"

            struct vertexInput {
                float4 vertex : POSITION;
                float4 texcoord0 : TEXCOORD0;
            };

            struct fragmentInput{
                float4 position : SV_POSITION;
                float4 texcoord0 : TEXCOORD0;
            };

            fragmentInput vert(vertexInput i){
                fragmentInput o;
                o.position = mul (UNITY_MATRIX_MVP, i.vertex);
                o.texcoord0 = i.texcoord0;
                return o;
            }
            float4 frag(fragmentInput i) : COLOR {
                return float4(i.texcoord0.xy,0.0,1.0);
            }
            ENDCG
        }
    }
}

Texture Coordinates sample

上記サンプルはテクスチャのUV座標がどういう値で渡ってくるかを視覚化したものです。
return部分を見てもらうと分かるように、テクスチャのUV座標をそのままRed, Greenに割り当てています。
画像を見ると、左上が(1.0, 0.0, 0.0, 1.0)、右上が(0.0, 0.0, 0.0, 1.0)、左下が(1.0, 1.0, 0.0, 1.0)、右下が(0.0, 1.0, 0.0, 1.0)となっているのが分かるかと思います。

Chess

Shader "Custom/TextureCoordinates/Chess" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct vertexInput {
                float4 vertex : POSITION;
                float4 texcoord0 : TEXCOORD0;
            };

            struct fragmentInput{
                float4 position : SV_POSITION;
                float4 texcoord0 : TEXCOORD0;
            };

            fragmentInput vert(vertexInput i){
                fragmentInput o;
                o.position = mul (UNITY_MATRIX_MVP, i.vertex);
                o.texcoord0 = i.texcoord0;
                return o;
            }

            float4 frag(fragmentInput i) : COLOR {
                float4 color;
                if ( fmod(i.texcoord0.x*8.0,2.0) < 1.0 ){
                    if ( fmod(i.texcoord0.y*8.0,2.0) < 1.0 )
                    {
                        color = float4(1.0,1.0,1.0,1.0);
                    } else {
                        color = float4(0.0,0.0,0.0,1.0);
                    }
                } else {
                    if ( fmod(i.texcoord0.y*8.0,2.0) > 1.0 )
                    {
                        color = float4(1.0,1.0,1.0,1.0);
                    } else {
                        color = float4(0.0,0.0,0.0,1.0);}
                    }
                return color;
            }
            ENDCG
        }
    }
}

Chess sample

上記サンプルは、チェスの盤面のような柄を表現するシェーダの例ですね。
texcoord0の値を参照しつつ、fmodを使って2.0で除算することで交互に色を出し分けている、というわけです。

fmodはモジュロの浮動小数点版で、第二引数で割った余りを返す関数です。(e.g. num % 2.0)
 つまり、2.0で割った値は0.0〜1.0にしかならないため、交互に色が出力されている、というわけです。

ChessOpt

ドキュメントにはもうひとつのチェスパターンを出力するシェーダが示されています。

Shader "Custom/TextureCoordinates/ChessOpt" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            float4 frag(v2f_img i) : COLOR {
                bool p = fmod(i.uv.x*8.0,2.0) < 1.0;
                bool q = fmod(i.uv.y*8.0,2.0) > 1.0;

                return float4(float3((p && q) || !(p || q)),1.0);
            }
            ENDCG
        }
    }
}

出力結果はほぼ同じです。
頂点シェーダのエントリ関数がvert_imgになったのが大きな違いです。
コード上にはないので、おそらくUnityCG.cgincファイル内で定義されているものと思われます。

Mandelbrot Fractal

Shader "Custom/TextureCoordinates/Mandelbrot" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #pragma target 3.0

            #include "UnityCG.cginc"

            float4 frag(v2f_img i) : COLOR {
                float2 mcoord;
                float2 coord = float2(0.0, 0.0);
                mcoord.x = ((1.0 - i.uv.x) * 3.5) - 2.5;
                mcoord.y = (i.uv.y * 2.0) - 1.0;

                float iteration = 0.0;
                const float _MaxIter = 29.0;
                const float PI = 3.14159;
                float xtemp;

                for (iteration = 0.0; iteration < _MaxIter; iteration += 1.0) {
                    if (coord.x * coord.x - coord.y * coord.y > 2.0 * (cos(fmod(_Time.y, 2.0 * PI)) + 1.0)) {
                        break;
                    }
                    xtemp = coord.x * coord.x - coord.y * coord.y + mcoord.x;
                    coord.y = 2.0 * coord.x * coord.y + mcoord.y;
                    coord.x = xtemp;
                }

                float val = fmod((iteration / _MaxIter) + _Time.x, 1.0);
                float4 color;

                color.r = clamp((3.0 * abs(fmod(2.0 * val, 1.0) - 0.5)), 0.0, 1.0);
                color.g = clamp((3.0 * abs(fmod(2.0 * val + (1.0 / 3.0), 1.0) - 0.5)), 0.0, 1.0);
                color.b = clamp((3.0 * abs(fmod(2.0 * val - (1.0 / 3.0), 1.0) - 0.5)), 0.0, 1.0);
                color.a = 1.0;

                return color;
            }

            ENDCG
        }
    }
}

マンデルブロ集合シェーダ

マンデルブロ集合を表現したシェーダです。

Wikipediaから引用すると、

マンデルブロ集合(マンデルブロしゅうごう、Mandelbrot set)とは、 複素平面上の集合、またはそれを複素平面上にプロットしたフラクタル図形。

ヨモツネットの記事でWebGLでそれを解説してくれているので、そちらを見てみるとより分かりやすいです。

Texture

最後は、単純にテクスチャを出力するシェーダです。

Shader "Custom/TextureCoordinates/Texture" {
    Properties {
        _MainTex("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;

            float4 frag(v2f_img i) : COLOR {
                return tex2D(_MainTex, i.uv);
            }

            ENDCG
        }
    }
}

texture sample

最後のはシンプルですね。
頂点シェーダから渡されたUV座標を使って、テクスチャから色をサンプリングしています。
色を取り出すのはtex2D関数です。

ちなみに、UnityCG.cgincを使わずに書くと以下のようになります。

Shader "Custom/TextureCoordinates/Texture" {
    Properties {
        _MainTex("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct vertexInput {
                float4 vertex : POSITION;
                float2 uv     : TEXCOORD0;
            };

            struct fragmentInput {
                float4 position : SV_POSITION;
                float2 uv       : TEXCOORD0;
            };

            uniform sampler2D _MainTex;

            fragmentInput vert(vertexInput v) {
                fragmentInput o;
                o.position = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }

            float4 frag(fragmentInput i) : COLOR {
                return tex2D(_MainTex, i.uv);
            }

            ENDCG
        }
    }
}

頂点シェーダで頂点位置とUV座標を受け取れるよう、POSITIONTEXCOORD0のセマンティクスを付けた構造体を定義します。
また、フラグメントシェーダの入力としてUV座標を受け取れるよう、同様にTEXCOORD0のセマンティクスを付けた構造体を定義し、それに、頂点シェーダで受け取った値をそのまま渡します。

そしてフラグメントシェーダでUV座標を元にテクスチャから色を取り出して完成です。