知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】頂点の位置を色で表す #22

前回の成果

今回やること

下記の記事で色がうまく取得できなかったので修正していきます。

soramamenatan.hatenablog.com

Vertex Surfaceソースコード

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"
}

こちらは以前と変更しません。

問題のソースコード(Vertex Fragment)

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 ShaderとFragment Shader
右がVertex ShaderとSurface Shaderです。 右が正しい動きです。

f:id:soramamenatan:20191007171027g:plain

問題点

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;
}

なぜ問題かというと、Clip座標系に変換してからampの差分を足しているからです。

Suface Shaderの内部

Surface ShaderにVertex Shaderをフックした場合、Clip座標系に明示的に変換していません。
なので、どちらが先に呼ばれるか中身を見て確認していこうと思います。

Surface Shaderの中身はこちらのサイト様に乗っています。

tips.hecomi.com

ベースパスの見出しのコードのvert_surfに注目してください。

v2f_surf vert_surf(appdata_full v)
{
    v2f_surf o;
    UNITY_INITIALIZE_OUTPUT(v2f_surf, o);

    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_TRANSFER_INSTANCE_ID(v, o);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    o.pos = UnityObjectToClipPos(v.vertex);
// 省略

71行目にClip座標に変換している処理がありました。

しかし、フックしたVertex Shaderはどこで呼ばれるのかが記載していません。

フックしたVertex Shaderの呼び先

docs.unity3d.com

Unityの公式に記載してありました。

vertex:VertexFunction - Custom vertex modification function. This function is invoked at start of generated vertex shader , and can modify or compute per-vertex data. See Surface Shader Examples.

とあります。
これは、フックした頂点シェーダーは頂点シェーダーの開始時に呼び出されるという意味です。
具体的にどこで呼び出されているのかは不明でしたが、これで順番が

  1. フックしたVertex Shaderが呼ばれる(ampの差分を足す)
  2. Clip座標系に変換される

ということがわかりました。

このことから先ほど問題点であげたことの意味が理解できたと思います。

修正後のソースコード

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.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                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);
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

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

ampの計算をしてからClip座標系に変換するように変更しました。

挙動

両方同じ挙動になりました。

f:id:soramamenatan:20191009190932g:plain

色が違う理由はSurface Shaderで自動でライティングの設定を行なっているからです。

Directional Light:ON f:id:soramamenatan:20191009192845p:plain

Directional Light:OFF f:id:soramamenatan:20191009192842p:plain

頂点座標で色を変更する

色の出力にも失敗していたので確認します。

Vertex Surfaceソースコード

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;
            float3 rgb;
        };

        // vert関数
        void vert(inout appdata_base v, out Input IN) {
            // 波の揺れを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);
            UNITY_INITIALIZE_OUTPUT(Input, IN);
            IN.rgb = float3(v.vertex.x / 10, v.vertex.y / 10, v.vertex.z / 10);
            IN.rgb.x = clamp(IN.rgb.x, 0, 1);
            IN.rgb.y = clamp(IN.rgb.y, 0, 1);
            IN.rgb.z = clamp(IN.rgb.z, 0, 1);
        }

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

Vertex Fragmentのソースコード

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;
                float3 rgb : NORMAL;
            };

            v2f vert(appdata_base v) {
                v2f o;
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                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);
                o.rgb = float3(v.vertex.x / 10, v.vertex.y / 10, v.vertex.z / 10);
                o.rgb.x = clamp(o.rgb.x, 0, 1);
                o.rgb.y = clamp(o.rgb.y, 0, 1);
                o.rgb.z = clamp(o.rgb.z, 0, 1);
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) :Color {
                fixed4 c = tex2D(_MainTex, i.uv);
                // 色出力用
                c.xyz = i.rgb.xyz;
                return c;
            }
            ENDCG
        }
    }
}

各構造体にrgb値を受け取れる変数を用意しています。

結果

色が同じになりました。

f:id:soramamenatan:20191009194034p:plain

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