おもちゃラボ

Unityで遊びを作っていきます

【Unity】シェーダを使って世界に雪を降らせよう

季節はどんどん夏に向かっていっていますが・・・今回はシェーダを使ってUnityで雪を降らせる方法を紹介したいと思います。

f:id:nn_hokuson:20170425190313j:plain

雪を積もらせるアルゴリズム

深さのある雪を積もらせる場合は色々と計算が大変ですが、表面に薄っすらと積もらせるだけであれば、テクスチャの色を変えるだけでそれっぽく見せることができます。

こんな、新緑(?)の風景が・・・

f:id:nn_hokuson:20170425192310j:plain:w450

こうなります!

f:id:nn_hokuson:20170425192258j:plain:w450

ただ、テクスチャの色を変えると言っても、テクスチャを真っ白にするだけでは画面一面真っ白になってしまうだけです。

屋根の裏側や葉っぱの裏側には雪を積もらせないようにするには、オブジェクトの法線と雪が降ってくるベクトル(大体上向き)の内積を取ります。

この内積の値が1に近ければば面が上を向いていると判断してテクスチャの色を白色にします。そうでない部分は面が横や下方向を向いているので、オリジナルのテクスチャ色を使用します。

f:id:nn_hokuson:20170425190605p:plain:w550

雪のシェーダプログラム

まずは雪用のシェーダファイルと、それに対応するマテリアルを作ります。プロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surface Shader」を選択、「Snow」という名前で保存します。作成したSnowシェーダを選択した状態で、右クリック→「Create」→「Material」でシェーダに対応するマテリアル(Custom_Snow)を作ります。

f:id:nn_hokuson:20170425191007p:plain:w150

作成したマテリアルは3Dモデルにアタッチして、テクスチャを設定しておきましょう。作成する雪のシェーダプログラムは次のようになります。

Shader "Custom/Snow" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Snow("Snow", Range(0,2))= 0.0
    }
    SubShader {
        Tags { }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows 
        #pragma target 3.0
        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        half _Snow;
    
        void surf (Input IN, inout SurfaceOutputStandard o) {
            float d = dot(IN.worldNormal, fixed3(0, 1, 0));
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            fixed4 white = fixed4(1,1,1,1);
            c = lerp(c, white, d*_Snow);

            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = 1;
            }
        ENDCG
    }
    FallBack "Diffuse"
}

まずは雪の積もらせ方をインスペクタから設定できるようにPropertiesにSnowパラメータ(0.0〜2.0)を追加しています。

surfメソッドの中ではまず、上方向のベクトルと面の法線ベクトルの内積をとって、面の上向き具合を調べています。この値とインスペクタから入力したSnowパラメータをかけた値が「雪積もり度」になります。

この「雪積もり度」により、上向きの面ほど雪が積もって白くなります。次の図からも上向きの面(樽や窓の桟)に雪が積もっていることがわかると思います。

f:id:nn_hokuson:20170425191422j:plain

実際には「雪積もり度」の値をもとにして、「設定されたテクスチャの色」と「雪の白色」の間の値をlerpメソッドで補間します。当然「雪積もり度」が大きいほど白色に近づき、「雪積もり度」が小さいほどテクスチャの元来の色に近づきます。

f:id:nn_hokuson:20170425191819j:plain:w500

木の裏側や屋根の裏側は、法線ベクトルと雪ベクトルの内積がマイナスになるので雪がつもりません。

f:id:nn_hokuson:20170425191727j:plain:w500

実行結果と雪の表現の参考資料

作成したSnowシェーダを実行した結果がこちらになります。ここではインスペクタからSnowのパラメータを0.0→1.0に変化させています。

f:id:nn_hokuson:20170425193625g:plain

アサシンクリードでも似たような手法を使っているようです。

【GDC 2013】「アサシンクリードIII」グラフィックス技術解説 - GAME Watch

雪がハラハラ舞うようにするには次のページが参考になりそうです。

qiita.com

雪の上に足跡をつけるアルゴリズムは「いけにえと雪のセツナ」の資料や・・・・

「いけにえと雪のセツナ」グラフィック解説(第1回・フロー編) – 株式会社ロジカルビート

http://japan.unity3d.com/unite/unite2016/files/DAY2_1030_room2_Domae.pdf

こちらの記事が参考になりそうです〜

kikikiroku.session.jp

全然参考にならない参考資料

「その気になればね、砂漠に雪を降らすことだって、余裕でできるんですよ」

あまり関係ないですが、伊坂幸太郎の「砂漠」がとても好きです。雪つながりということで・・・笑

砂漠 (新潮文庫)

砂漠 (新潮文庫)