知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】風で揺れているような表現 #20

前回の成果

カリングがわかった。

soramamenatan.hatenablog.com

今回やること

旗が風で揺れているような見た目を制作します。

nn-hokuson.hatenablog.com

事前準備

SceneにPlaneを用意して、カメラから見ていい感じに斜めになるようにしてください。

f:id:soramamenatan:20190902115551p:plain

ソースコード

Shader "Unlit/shake" {
    Properties {
        _MainTex("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert vertex:vert
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        void vert(inout appdata_base v) {
            float amp = 0.5f * sin(_Time * 100 + v.vertex.x * 100);
            v.vertex.xyz = float3(v.vertex.x, v.vertex.y + amp, v.vertex.z);
        }

        void surf(Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

揺らすための考え方

Planeのオブジェクトを横からみると頂点が一直線にならんでいます。

f:id:soramamenatan:20190902140337p:plain

【Unityシェーダ入門】シェーダで旗や水面をなびかせる - おもちゃラボ:より引用

この頂点を上下に移動させることによって、Planeの形を波のようにさせます。

f:id:soramamenatan:20190902140340p:plain

【Unityシェーダ入門】シェーダで旗や水面をなびかせる - おもちゃラボ:より引用

それぞれの頂点は単に上下移動しているだけですが、隣の頂点とは少しタイミングをずらしています。
これで風で揺れているようにします。

f:id:soramamenatan:20190902140345p:plain

【Unityシェーダ入門】シェーダで旗や水面をなびかせる - おもちゃラボ:より引用

#pragma surface surf Lambert vertex:vert

これで、Vertex Shaderをフックすることができます。

フック (hook)とは

プログラムの中に独自の処理を割りこませるために用意されている仕組み。

もしくは

プログラムにおいて、本来の処理を横取りして独自の処理を割りこませること

です。

https://wa3.i-3-i.info/word12296.html:より引用

これにより、Surface ShaderでVertex Shaderを使用することができます。

今回はライティングを特に使用しないため、Lambertにしています。
ライティングに関してはこちらを参照してください。

soramamenatan.hatenablog.com

void vert(inout appdata_base v)

これがVertex Shaderです。
今回はvertex:POSITIONしか使用しないので、appdata_baseを使用しています。

appdata一覧はこちら

soramamenatan.hatenablog.com

ちなみに、Input構造体のデータを受け取りたい場合は、

void vert(inout appdata_full v, out Input o)

代わりにこちらを定義すれば、使用することができます。
その際に

UNITY_INITIALIZE_OUTPUT(Input, o);

を忘れないようにしてください。
UNITY_INITIALIZE_OUTPUTはInputを初期化する関数です。

float amp = 0.5f * sin(_Time * 100 + v.vertex.x * 100);

この計算式がPlaneを揺らしている本体となります。

sin関数を使用することによって、-1~1の値を往復させて、揺れを表現しています。

そして、隣の頂点とは少しズラすために頂点のx座標を足しています。

結果

f:id:soramamenatan:20190903185620g:plain

テクスチャは適当なものを貼り付けています。

ソースコードにコメントを付与

Shader "Unlit/shake" {
    // プロパティ
    Properties {
        // テクスチャ
        _MainTex("Texture", 2D) = "white" {}
    }
    // Shaderの中身を記述
    SubShader {
        // 一般的なShaderを使用
        Tags { "RenderType" = "Opaque" }
        // しきい値
        LOD 200

        // cg言語記述
        CGPROGRAM
        // vertex shaderをフックする
        #pragma surface surf Lambert vertex:vert
        // Shader Model
        #pragma target 3.0

        // テクスチャ
        sampler2D _MainTex;

        // Input構造体
        struct Input {
            float2 uv_MainTex;
        };

        // vert関数
        void vert(inout appdata_base v) {
            // 波の揺れをsin関数を用いて表現
            float amp = 0.5f * sin(_Time * 100 + v.vertex.x * 100);
            v.vertex.xyz = float3(v.vertex.x, v.vertex.y + amp, v.vertex.z);
        }

        // surf関数
        void surf(Input IN, inout SurfaceOutput o) {
            // テクスチャのピクセルの色を返す
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

今回はこれで終わりです。
ここまでご視聴ありがとうございました!

せっかくなので、Vertex Shaderと Flagment Shaderでも書いてみた。

Shader "Unlit/shakeVert" {
    Properties{
        _MainTex("Texture", 2D) = "white" {}
    }
    SubShader{
            Tags{
                "Queue" = "Geometry"
                "RenderType" = "Opaque"
            }
    Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            v2f vert(appdata_base v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                float amp = 0.5f * sin(_Time * 100 + v.vertex.x * 100);
                o.vertex.xyz = float3(o.vertex.x, o.vertex.y + amp, o.vertex.z);
                return o;
            }

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

結果

左がvertex to fragmentで右がvertex to surfaceです。

f:id:soramamenatan:20190904141536g:plain

渡している値は同じなのに右のオブジェクトの方が激しく揺れています。
また、明度も左右によって違ってきます。

なぜ変わるのか

自分なりに解決してみた。
違うところがあればご指摘してください。

明度

surfaceの方は

#pragma surface surf Lambert vertex:vert

と明示的に拡散を示しているのに対し、fragmentの方は暗示的に行なっているので変わっているのではないか。

揺れの大きさ

試しに両者のappdata_baseを色として出力してみると、

f:id:soramamenatan:20190904142600p:plain

となったので、そもそも取得してきている値が異なっているのではないかと思う。
右側がなぜ4色になるのかは不明