おもちゃラボ

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

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

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

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

砂漠 (新潮文庫)

砂漠 (新潮文庫)

【Unity】Animation Clipで設定した位置を相対的な値として扱う方法

AnimatorでAnimation Clipを動かすと、アニメーションの位置はAnimation Clipに設定されている値が優先されて表示されます。

例えば、原点でバウンドするボールのアニメーションを作ると、その後でボールを移動させても、実行時には原点でバウンドするボールに戻ってしまうのです。

f:id:nn_hokuson:20170424192512j:plain

Unity5ではRoot Motionを設定することで、Animation Clipで設定したアニメーションの位置を相対的なものとして扱うことができます。ここではRoot Motion機能を使って移動後の場所でアニメーションを再生する方法を紹介します。

アニメーションを作成する

まずはボールが跳ねるアニメーションを作りましょう。

シーンビューに球を配置して、その球を選択した状態でメニューバーから「Window」→「Animation」を選択します。出てきたAnimation Windowの「Create」ボタンをクリックしてhopという名前で保存しておきます。

f:id:nn_hokuson:20170424192527p:plain:w400

続いて「Add Property」→「Transform」→「Position」を選択して、アニメーションを作成します。今回はアニメーションカーブを次のように配置してみました。

f:id:nn_hokuson:20170424192535p:plain:w500

さてこの状態で実行してみると、原点を軸にしてボールがバウンドします。このボールを無理やり端っこに持っていってもやはり原点でバウンドしてしまいます。

f:id:nn_hokuson:20170424192631j:plain:w400

Generate Root Motion Curbを設定する

Root Motionを設定することで、Animation Clipに設定された値を相対的なものとして扱えるようになります。ここでは球にRoot Motionを設定して、移動先でバウンドするようにしましょう。

まずは作成した「hop」のAnimation Clipを選択し、インスペクタから「Generate Root Motion Curb」のボタンをクリックします。

f:id:nn_hokuson:20170424192709p:plain:w300

続いて、SphereにアタッチされているAnimatorコンポーネントの詳細をインスペクタに表示し、「Apply Root Motion」のチェックを入れます。

f:id:nn_hokuson:20170424192733p:plain:w300

これによりRoot Motionが有効になります。ゲームを実行してみると移動後の場所でボールがバウンドするようになります。

f:id:nn_hokuson:20170424192738g:plain

まとめ

Animatorを使った場合、通常ではAnimation Clipに設定された座標が優先されて使われます。Root Motionを使うことで、オブジェクトを移動しても移動先でアニメーションを再生することができるようになります。

AnimatorやAnimation Clipに関しては「Unity5の教科書」でも説明していますので、あわせて参考にしてみてくださいね^^

Unity5の教科書 (Entertainment&IDEA)

Unity5の教科書 (Entertainment&IDEA)

【Unity】オブジェクトを破棄するタイミングで効果音を鳴らしたい

オブジェクトを破棄(destroy)する直前で効果音を鳴らそうとしても、オブジェクトが破棄されると、AudioSourceコンポーネントもあわせて破棄されてしまうので効果音が鳴りません。 
 
たとえば、シューティングゲームでは、ミサイルが敵にあたったタイミングで、爆発音を鳴らしますね。この場合、敵もミサイルも破棄するので、どちらのオブジェクトにAudioSourceコンポーネントをアタッチしても爆発音は鳴りません。

f:id:nn_hokuson:20170421195844p:plain:w450

この対策として、破棄するオブジェクトとは別に効果音用のオブジェクトを作っておき、そのオブジェクトで効果音を鳴らすというのが常套手段かもしれません。ただ、小さいデモ用のプロジェクトでこれをするのは、すこし面倒です。
 
そんなときに使えるのが、PlayClipAtPointメソッドです。このメソッドは、鳴らしたいオーディオクリップと座標を引数に指定すると、指定した場所に新しく一時オブジェクトを生成し、効果音を鳴らしてくれます。

引数 意味
clip 再生するためのオーディオデータ
position 音の発生源からのワールド空間内の位置
volume 再生時の音量

 
しかもしかも!効果音の再生が終わったら、一時オブジェクトは自動的に破棄されるのでゴミオブジェクトが残ることもありません。
 f:id:nn_hokuson:20170421201258j:plain

たとえばミサイルクラスで効果音を鳴らすには、次のようなプログラムになります。

public class MissileController : MonoBehaviour 
{
    // 爆発効果音
    public AudioClip explosionSE;
        
    void OnCollisionEnter2D(Collision2D other)
    {
        if( other.gameObject.tag == "Enemy" )
        {
             // オーディオを再生
             AudioSource.PlayClipAtPoint( explosionSE, transform.position);
             // ミサイルオブジェクトを破棄
             Destroy(gameObject);
        }
    }
}

  
PlayClipAtPointメソッドは案外知られていませんが、unityでサクッと試作を作りたいときには、重宝するメソッドですね!
 

シューティングゲームアルゴリズムマニアックス (C magazine)

シューティングゲームアルゴリズムマニアックス (C magazine)