知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】雪を積もらせる #37

前回の成果

レイマーチングでモーフィングを実装した。

soramamenatan.hatenablog.com


今回やること

今回は雪を実装していきます。

  • 積もる表現
  • 雪が降る表現

の2つを実装します。
先に積もる表現から行っていきます。

nn-hokuson.hatenablog.com


事前準備

下記のサイトから家とタルのアセットをダウンロードして、Scene上に配置してください。

assetstore.unity.com

Hierarchyのキャプチャ

f:id:soramamenatan:20200125153404p:plain

Sceneのキャプチャ

f:id:soramamenatan:20200125153407p:plain

また、今回制作したmaterialのTextureに、家のTextureをアタッチしてください。
樽も同じように制作してください。
f:id:soramamenatan:20200125173641p:plain


ソースコード

Shader "Unlit/snowPile" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _SnowPileValue("SnowPileValue", Range(0, 3)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half _SnowPileValue;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                float d = dot(i.normal, fixed3(0, 1, 0));
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 white = fixed4(1, 1, 1, 1);
                col = lerp(col, white, d * _SnowPileValue);
                return col;
            }
            ENDCG
        }
        Pass {
            Name "CastShadow"
            Tags { "LightMode" = "ShadowCaster" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            struct v2f {
                V2F_SHADOW_CASTER;
            };

            v2f vert( appdata_base v ) {
                v2f o;
                TRANSFER_SHADOW_CASTER(o)
                return o;
            }

            float4 frag( v2f i ) : COLOR {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}

1Pass目は法線を使っているのがわかると思います。
しかし、2Pass目が不明です。


雪の表現

今回制作している雪ですが、正確には積もるではなくの上の方が白くなるものです。
手法としては簡単で、

float d = dot(i.normal, fixed3(0, 1, 0));

上記のソースで、法線方向と、上ベクトルの内積でTextureの色を判断しています。

f:id:soramamenatan:20200125154236p:plain

【Unityシェーダ入門】シェーダを使って世界に雪を降らせよう - おもちゃラボ:より引用


2Pass目の意味

解説に入る前に2Pass目で具体的に何をしているのかを説明します。
今使用しているUnlitShaderはライトの影響を受けないShaderなので、不自然になってしまいます。

SurfaceShader

f:id:soramamenatan:20200125155711p:plain

UnlitShader

f:id:soramamenatan:20200125155714p:plain

上の2枚の画像からわかるように、ライトの影響を受けないのでオブジェクトに影が落ちません。
しかし、2Pass目の処理を追加することによって影が落ちるようになります。

UnlitShaderに2Pass目の処理を追加

f:id:soramamenatan:20200125155718p:plain

家には影が落ちていませんがまだましになります。


Name

Name "CastShadow"

パスに名前をつけています。
これをすることによって、他のShaderから参照できるようになります。


ShadowCaster

Tags { "LightMode" = "ShadowCaster" }

オブジェクトの深度をシャドウマップや深度テクスチャに描画することができます。


#pragma multi_compile_shadowcaster

シャドウマップに書き込む際に必要となるpragmaです。


V2F_SHADOW_CASTER

Unityの内部的にfloat4 pos : SV_POSITIONを指定しているものです。
下記のソースはV2F_SHADOW_CASTERの定義となります。

#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS float4 pos : SV_POSITION


TRANSFER_SHADOW_CASTER(o)

オブジェクトのワールド座標をむにゃむにゃしています。
自分では理解できなかったので、わかる情報だけ載せておきます。
こちらは内部的に、

ポイントライトの場合
#define TRANSFER_SHADOW_CASTER(o) o.vec = mul( _Object2World, v.vertex ).xyz - _LightPositionRange.xyz; o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
スポットライトとディレクショナルライトの場合
#define TRANSFER_SHADOW_CASTER(o) o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.pos.z += unity_LightShadowBias.x; \
float clamped = max(o.pos.z, o.pos.w*UNITY_NEAR_CLIP_VALUE); o.pos.z = lerp(o.pos.z, clamped, unity_LightShadowBias.y);

となっているので今回は下の定義を利用しています。

unity_LightShadowBias

この関数に関してはかなり曖昧なので、あまり参考にしないでください。
ディレクショナルライトのBiasの情報を集約しているもの

Bias

f:id:soramamenatan:20200125170810p:plain

赤枠で囲ってあるものがBias。

Biasが0(最小値)

f:id:soramamenatan:20200125171348p:plain

Biasが2(最大値)

f:id:soramamenatan:20200125171352p:plain

unity_LightShadowBias.x
min(カメラのファークリップ, 影の距離 - 影のnear planeのオフセット)

の線形関係

unity_LightShadowBias.y

ポイントライトとスポットライトの場合は0。
ディレクショナルライトの場合は1。

unity_LightShadowBias.z

ユーザーが指定している通常のオフセット量を含む、ワールド空間のテクセルサイズによってスケーリングされるもの。
normal Biasのこと。

参考サイト様

https://forum.unity.com/threads/how-to-unpack-unity_lightshadowbias-and-_worldspacelightpos0-values.450382/

Unity-Built-in-Shaders/UnityCG.cginc at master · TwoTailsGames/Unity-Built-in-Shaders · GitHub


SHADOW_CASTER_FRAGMENT(i)

これは視点空間(view)の深度を表しているものです。

ポイントライトの場合
#define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
スポットライトとディレクショナルライトの場合
#define SHADOW_CASTER_FRAGMENT(i) UNITY_OUTPUT_DEPTH(i.hpos.zw);
UNITY_OUTPUT_DEPTH

Eye Space Depth(視点空間の深度)を返すものです。
デプステクスチャのあるプラットフォームでは、Zバッファの値が暗黙的にレンダリングされるため常に0を返します。
今回の場合は0を返しています。

参考サイト様

docs.unity3d.com


結果

下記の画像のように、オブジェクトの上の部分が白くなれば成功です。
画像では、SnowPileValueの値を3に設定しています。

f:id:soramamenatan:20200125174344p:plain


また、このようなScriptを用意して各オブジェクトにアタッチすれば

using UnityEngine;

public class PileSnow : MonoBehaviour {
    private float _snow;
    void Update() {
        _snow += 0.005f;
        var renderer = GetComponent<Renderer>();
        _snow = Mathf.Clamp(_snow, 0, 3);
        renderer.material.SetFloat("_SnowPileValue", _snow);
    }
}

徐々に積もっていくような表現ができます。

f:id:soramamenatan:20200125174603g:plain


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

Shader "Unlit/snowPile" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _SnowPileValue("SnowPileValue", Range(0, 3)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half _SnowPileValue;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                // オブジェクトの法線ベクトルと上方向のベクトルでの内積で積もる場所を計算
                float d = dot(i.normal, fixed3(0, 1, 0));
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 white = fixed4(1, 1, 1, 1);
                col = lerp(col, white, d * _SnowPileValue);
                return col;
            }
            ENDCG
        }
        Pass {
            Name "CastShadow"
            // シャドウマップや深度テクスチャに描画
            Tags { "LightMode" = "ShadowCaster" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // シャドウマップに書き込む
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            struct v2f {
                // SV_POSITION
                V2F_SHADOW_CASTER;
            };

            v2f vert( appdata_base v ) {
                v2f o;
                // lightのbiasをワールド座標と計算
                TRANSFER_SHADOW_CASTER(o)
                return o;
            }

            float4 frag( v2f i ) : COLOR {
                // viewの深度
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}


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