知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】氷のようなShader #7

前回の成果

soramamenatan.hatenablog.com

#pragmaが理解できた。

今回やること

おもちゃラボ様を参考に氷のようなシェーダーを作ります。

nn-hokuson.hatenablog.com

もうシェーダーらしさ出てますね!


ソースコード

Shader "Custom/ice" {
    SubShader {
        Tags { "Queue" = "Transparent"}
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard alpha:fade
        #pragma target 3.0

        struct Input {
            float3 worldNormal;
            float3 viewDir;
        };

        void surf(Input IN, inout SurfaceOutputStandard o) {
            o.Albedo = fixed4(1,1,1,1);
            float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
            o.Alpha = alpha * 1.5f;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

こちらが今回のソースコードになります。


Tags { "Queue" = "Transparent"}

#pragma surface surf Standard alpha:fade

から透過処理を行いたいことが理解できると思います。
しかし、

void surf(Input IN, inout SurfaceOutputStandard o) {
    o.Albedo = fixed4(1,1,1,1);
    float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
    o.Alpha = alpha * 1.5f;
}

ここを理解するのが今回の目的となります。


abs?? dot??

アルファ値の値を変更しようとしてるのはわかるけど...。
absとdotとは何者なんだ、となったと思います。


abs

absoluteのことです、つまり絶対値です。
使用方はいたって簡単で、

// xには絶対値を取得したい数字を入れる
abs(x);

以上です。

簡単に言えば数値のマイナスを取って返してくれるもので、例を挙げると

int x = 10;
float y = -15.5;

// 10が返ってくる
abs(x);
// 15.5が返ってくる
abs(y);

このような感じです。


dot

内積のことです。
内積を使用することによって、worldNomal(1ピクセル毎の法線)とviewDir(カメラからの視線)の2つのベクトルが交わる角度を取得することができます。


f:id:soramamenatan:20190621112707p:plain ベクトル演算を理解する - Unity マニュアル:より引用

この画像をみると一目瞭然ですね。
dotを使用すると返ってくる値はかっこ内の数値です。

2ベクトル間の角度 返ってくる値
1
90° 0
180° -1

この返ってくる値をアルファ値に代入して、透明感を出そうという考え方となります。


今回の成果

f:id:soramamenatan:20190621154313p:plain

これで氷シェーダーの完成です!

ソースコードにコメント

Shader "Custom/ice" {
    SubShader {
        // 半透明オブジェクトを一番最後に描画する
        Tags { "Queue" = "Transparent"}
        // あとで理解する
        LOD 200

        // あとで理解する
        CGPROGRAM
        // 透過させる
        #pragma surface surf Standard alpha:fade
        // あとで理解する
        #pragma target 3.0

        // input構造体
        struct Input {
            // 1ピクセル毎の法線
            float3 worldNormal;
            // カメラからの視線
            float3 viewDir;
        };

        // surf関数
        void surf(Input IN, inout SurfaceOutputStandard o) {
            // ベースカラーの変更
            o.Albedo = fixed4(1,1,1,1);
            // アルファ値を法線と視線の2つのベクトルから取得
            float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
            // 見た目の調整で*1.5
            o.Alpha = alpha * 1.5f;
        }
        // あとで理解する
        ENDCG
    }
    // あとで理解する
    FallBack "Diffuse"
}

今回は以上となります!
次回もおもちゃラボ様を参考にシェーダーを書いていこうと思います!


おまけ

あとで理解するものが多くなってきたので少しずつ紹介していきます。


LOD

LODとはLevel of Detailの略で、LOD値がこちらで決めたしきい値以下の場合に、そのシェーダーを使用するものです。
例えば、iPhone5とiPhoneXRがあるとします。
2機間でスペックが違うので、iPhone5では描画コストが低いシェーダーを、iPhoneXRでは描画コストが高いシェーダーを描画させたいときにLODを使用することで、シェーダーを切り替えることができます。

やってみる

www.shibuya24.info

このサイト様を参考に実装していこうと思います。
まずはシェーダー側のソースコードです。

// LOD値の高い順からSubShaderを記述する必要がある
Shader "Custom/LOD" {
    Properties{}
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 10

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

            fixed4 frag (v2f_img i) : SV_Target {
                return fixed4(0, 0, 1, 1);
            }
            ENDCG
        }
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 5

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

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

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 3

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

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

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 1

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

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

このシェーダーで注目してほしいのは、LODの値によって、色が変化しているところです。
次に、Script側のソースコードです。

using UnityEngine;

public class LODTest : MonoBehaviour {

    // 色を変更させるオブジェクトのレンダラー
    [SerializeField]
    private Renderer objRenderer;

    // カメラの位置
    [SerializeField]
    private Transform _cameraTransform;

    void Update() {
        // シェーダにLOD値を代入
        objRenderer.sharedMaterial.shader.maximumLOD = (int) Mathf.Abs(_cameraTransform.localPosition.z);
    }
}

このLODTest.csを空のGameObjectにアタッチしてあげてください。
そして、
objRendererにはオブジェクトを、
cameraTransformにはカメラをアタッチしてください。
f:id:soramamenatan:20190621170518p:plain


Unityを実行してカメラのz座標を変更すると・・・

f:id:soramamenatan:20190621174323g:plain

色が変わっていますね!
z座標が1未満になると1未満のLODがないので、描画されなくなってしまいます。

おまけ終わり。