【Unity/パーティクル】雨を作る方法

Unityで雨を作り、STYLYへアップロードするまでの手順を解説します。 ※サンプルプロジェクトはこちらから:https://github.com/styly-dev/STYLY-Unity-Examples/tree/master/Assets/STYLY_Examples/Rain

Unityの頂点シェーダーを使った雨のサンプル

Unityの頂点シェーダーを使った雨のサンプル

 

雨の作り方の解説

雨の作成に入る前に、どうやって雨を作るのかを軽く解説いたします。 以下の3STEPで雨を作ります。

  1. 三角形メッシュを大量に作成
  2. 並べた三角形メッシュを高速で揺らす
  3. 色を調整すると雨っぽく見えます(完成)

三角形をメッシュを大量に作成します

三角形メッシュをプログラムで作成

三角形メッシュをプログラムで作成

立方体を埋める形で大量の細かい三角形メッシュを並べています。

三角形メッシュを高速で揺らします

頂点シェーダーで三角形メッシュを揺らす

頂点シェーダーで三角形メッシュを揺らす

頂点シェーダーで頂点を揺らします

色味を調整すると雨っぽい(完成)

色を調整し、雨っぽい見た目にして完成

色を調整し、雨っぽい見た目にして完成

メッシュを水色の半透明にすると雨っぽい見た目になって完成です。  

メッシュ作成

ここから、実際に雨を作る手順の解説をしていきます。   まずはメッシュを作成します。

プログラムで大量の三角形メッシュを作成

プログラムで大量の三角形メッシュを作成

準備

今回は三角形メッシュを作成するために2つのC#スクリプトを使用します。 ここでは、2つのC#スクリプトを追加する手順を解説します。  

スクリプト1 : RandomMesh.cs の作成

まずはProjectウィンドウ上で右クリックしてC#スクリプトを作成します。

C#スクリプト作成

C#スクリプト作成

  名前をRandomMeshにします。

作成したスクリプトの確認

作成したスクリプトの確認

  スクリプトRandomMeshを開き、中身を以下のように書き換えます。

using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class RandomMesh : MonoBehaviour
{
    const string k_MeshName = "[Generated]RainMesh"; // メッシュの名前
    [SerializeField, Header("雨粒の数")] int m_TriangleCount = 1500; // 三角形の個数
    [SerializeField, Header("雨粒の大きさ")] float m_TriangleScale = 0.3f; // 三角形の大きさ
    [SerializeField, Header("雨全体のバラつき")] Vector3 m_TriangleRange = new Vector3(4f, 4f, 4f); // メッシュの大きさ
    [SerializeField, HideInInspector] string m_OnCreateText = "メッシュ情報がありません\nメッシュを更新してください";

    public int TriangleCount { get { return m_TriangleCount; } }
    public float TriangleScale { get { return m_TriangleScale; } }
    public Vector3 TriangleRange { get { return m_TriangleRange; } }
    public string OnCreateText { get { return m_OnCreateText; } set { m_OnCreateText = value; } }

    /// <summary>
    /// メッシュの新規作成 
    /// </summary>
    public Mesh CreateNewMesh()
    {
        Vector3[] vertices = new Vector3[m_TriangleCount * 3]; // 頂点の座標
        int[] triangles = new int[m_TriangleCount * 3]; // 頂点インデックス
        Vector3[] normals = new Vector3[vertices.Length];
        int pos = 0;
        for (int i = 0; i < m_TriangleCount; i++)
        {
            var v1 = Vector3.Scale(new Vector3(Random.value, Random.value, Random.value) - Vector3.one * 0.5f, m_TriangleRange);
            var v2 = v1 + new Vector3(Random.value - 0.5f, 0f, Random.value - 0.5f) * m_TriangleScale;
            var v3 = v1 + new Vector3(Random.value - 0.5f, 0f, Random.value - 0.5f) * m_TriangleScale;

            vertices[pos + 0] = v1;
            vertices[pos + 1] = v2;
            vertices[pos + 2] = v3;
            pos += 3;
        }

        for (int i = 0; i < triangles.Length; i++)
        {
            triangles[i] = i;
        }

        for (int i = 0; i < normals.Length; i++)
        {
            normals[i] = new Vector3(0f, 1f, 0f);
        }
        
        //メッシュ生成
        var mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.normals = normals;

        return mesh;
    }

}

スクリプトRandomMeshはこれで完成です。  

スクリプト2 : RandomMeshInspector.cs の作成

同様の手順で、スクリプトRandomMeshInspectorも作成します。

スクリプトを開く

スクリプトを開く

  スクリプトRandomMeshInspectorの中身を以下のように書き換えます。

using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.IO;

// エディター拡張
[CustomEditor(typeof(RandomMesh))]
public class RandomMeshInspector : Editor
{
    const string k_ExportMeshName = "[Generated]RainMesh"; // 保存するメッシュの名前
    static readonly Color k_ButtonColor = Color.yellow;
    static readonly Color k_ButtonTextColor = Color.white;

    /// <summary>
    /// インスペクタの描画
    /// </summary>
    public override void OnInspectorGUI()
    {
        if (PrefabUtility.GetPrefabType(target) != PrefabType.Prefab)
        {
            EditorGUILayout.HelpBox("雨の設定はPrefab上でのみ可能です。 \nYou must edit settings via Prefab.", MessageType.Info);
            return;
        }

        var defaultColor = GUI.color;
        var defaultContentColor = GUI.contentColor;

        var randomMesh = target as RandomMesh;

        // メッシュ情報 表示
        EditorGUILayout.BeginVertical("Box");
        EditorGUILayout.LabelField(randomMesh.OnCreateText, GUILayout.Height(54f));
        EditorGUILayout.EndVertical();

        // 色を変更
        GUI.color = k_ButtonColor;
        GUI.contentColor = k_ButtonTextColor;

        if (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab)
        {
            if (GUILayout.Button("メッシュの更新"))
            {
                EditorCreateMesh();
            }
        }

        // 元に戻す
        GUI.color = defaultColor;
        GUI.contentColor = defaultContentColor;

        base.DrawDefaultInspector();
    }

    /// <summary>
    /// メッシュを作成し、保存する
    /// </summary>
    [ContextMenu("Create Mesh")]
    void EditorCreateMesh()
    {
        var randomMesh = target as RandomMesh;
        var meshFilter = randomMesh.GetComponent<MeshFilter>();

        // 古い内蔵メッシュを削除
        var meshAssets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(randomMesh));
        foreach (var meshAsset in meshAssets)
        {
            if (meshAsset is Mesh)
            {
                Object.DestroyImmediate(meshAsset, true);
            }
        }

        // メッシュ作成
        var newMesh = randomMesh.CreateNewMesh();
        newMesh.name = k_ExportMeshName;
        meshFilter.sharedMesh = newMesh;

        // メッシュを内蔵させる
        AssetDatabase.AddObjectToAsset(newMesh, randomMesh);

        randomMesh.OnCreateText = string.Format(
            "【現在のメッシュ情報】\n・雨の粒の数:{0}\n・雨の粒の大きさ:{1}\n・雨全体の大きさ: {2}",
            randomMesh.TriangleCount,
            randomMesh.TriangleScale,
            randomMesh.TriangleRange
        );

        // 保存
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }


    /// <summary>
    /// メッシュの保存パスを生成(Prefabと同階層)
    /// </summary>
    string GenerateMeshPath()
    {
        var path = AssetDatabase.GetAssetPath(target);
        var parentFullPath = Directory.GetParent(path).FullName;
        var parentPath = "Assets" + parentFullPath.Substring(Application.dataPath.Length);
        var assetPath = parentPath + Path.DirectorySeparatorChar + k_ExportMeshName; // アセットの保存先パス
        return assetPath;
    }
}

以上で2つのC#スクリプトの作成は完了です。  

実際にメッシュを作ってみる

次に、三角形メッシュを実際に作ってみます。

三角形メッシュ

三角形メッシュ

 

Rainオブジェクトの作成

HierarchyウィンドウでGameObjectを作成します.

空オブジェクトを作成

空オブジェクトを作成

  オブジェクトの名前をRainにします.

名前の変更

名前の変更

 

名前変更後のオブジェクト

名前変更後のオブジェクト

 

コンポーネントRandomMeshをくっつける

RainオブジェクトにRandomMeshコンポーネントを追加します。

コンポーネントRandomMeshの追加

コンポーネントRandomMeshの追加

  コンポーネントを追加するとInspectorが以下のような表示になります

追加前と追加後のInspectorウィンドウの比較

追加前と追加後のInspectorウィンドウの比較

 

コンポーネントの確認

コンポーネントの確認

RainオブジェクトをPrefab化する

次にRainオブジェクトをPrefab化します。   HierarchyウィンドウのRainオブジェクトをProjectウィンドウへドラッグ&ドロップします

ドラッグアンドドロップでPrafabを作成

ドラッグアンドドロップでPrafabを作成

  Prefabが作成されます。

作成されたPrefab

作成されたPrefab

 

メッシュ作成

RainのPrefabを選択した状態でInspectorウィンドウ上の「メッシュの更新」ボタンをクリックします。

ボタンを押してメッシュ更新

ボタンを押してメッシュ更新

  三角形メッシュがたくさん表示されます。

作成されたメッシュ

作成されたメッシュ

 

メッシュがピンク色になる原因

メッシュの色がピンク色になっていますが、これはRainオブジェクトのMeshRendererに マテリアルが登録されていないことが原因です。

マテリアルの有無の比較

マテリアルの有無の比較

マテリアルを登録することで色味を調整することができるようになります。  

MeshRenderにマテリアルを登録する

MeshRendererへマテリアルを登録する手順を解説します。   MeshRendererのMaterialsの部分をクリックします。

Materialsをクリックして開く

Materialsをクリックして開く

 

マテリアルの登録箇所

マテリアルの登録箇所

  Materialsの丸ポチの部分をクリックすると、マテリアル一覧ウィンドウが表示されるので 登録したいマテリアルを選択します。

Default-Diffuseマテリアルを登録

Default-Diffuseマテリアルを登録

Default-Diffuseを選択するとDefault-DiffuseマテリアルがMeshRendererに登録されます。  

マテリアル登録後の三角形メッシュ

マテリアル登録後の三角形メッシュ

Default-Diffuseマテリアルを登録した場合、白色になります。  

三角形メッシュに動きを与える

次にメッシュに動きを与えることでより雨らしくなります。   ここでは、三角形メッシュに動きを与える手順を解説します。  

カスタムシェーダーで動きを作る

今回は、カスタムシェーダーを利用して頂点を動かし、雨っぽい動きを与えます。

三角形メッシュとカスタムシェーダーを組み合わせて雨を作る

三角形メッシュとカスタムシェーダーを組み合わせて雨を作る

 

カスタムシェーダーの作成

ここからは実際にカスタムシェーダーを作成する手順を解説します。

Rainシェーダー

Rainシェーダー

  まずはProjectウィンドウのCreateをクリックします。(Projectウィンドウ上での右クリックでもOK)

Createをクリック

Createをクリック

  Shader/Standard Surface Shaderを選択します。

Standard Surface Shaderを選択

Standard Surface Shaderを選択

  シェーダが出現するので、名前をRainにします

シェーダーの名前をRainにする

シェーダーの名前をRainにする

 

シェーダーファイルの編集

シェーダーRainを開きます。

シェーダーRainをダブルクリック

シェーダーRainをダブルクリック

  シェーダーRain.shaderを以下のように書き換えます。

Shader "Custom/Rain" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Speed ("[VS]雨が落ちる速さ", Float) = 6.0 
        _Scale ("[VS]雨の高さ", Float) = 4.0
		_Remap("[VS]Remap", Range(0.0, 1.0)) = 0.7
        [Space]
        _Albedo ("[FS]雨 色", Color) = (1.0, 1.0, 1.0, 0.5) 
        _Emission ("[FS]雨 発光色", Color) = (0.0, 0.0, 0.0, 0.0) 
        _Specular ("[FS]雨 スペキュラーパワー", Range(0.0, 1.0)) = 0.5 
        _Gloss ("[FS]雨 スペキュラー強度", Range(0.0, 1.0)) = 0.5 
	}

    SubShader {
        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }
        CGPROGRAM
        #pragma surface surf Lambert alpha
        #pragma vertex vert

        sampler2D _MainTex;

        // [0;1]のランダムな値を返す関数
        float nhash11(float n){
            return frac(sin(n) * 43758.5453);
        }

        // 値域[a;b] を 値域[0;1]へ変換する関数
        float remap(float t, float a, float b){
            return clamp((t-a)/(b-a), 0, 1);
        }
        // 頂点シェーダー
        half _Speed;
		half _Scale;
		half _Range;
		fixed _Remap;

        // 色
        half4 _Albedo;
        half4 _Emission;
        fixed _Specular;
        fixed _Gloss;


        // 頂点シェーダー関数
        void vert(inout appdata_full v) {
            fixed rnd = nhash11(fmod(v.vertex.z, 512.0)); // ランダム値
			//float timer = _Time.w * _Speed * remap(0.7, 1.0, rnd);
			float timer = _Time.w * _Speed * remap(_Remap, 1.0, rnd); // 値域の変換 [_Remap, 1.0] -> [0;1]
            v.vertex.y -= fmod(-v.vertex.y + timer, _Scale) + v.vertex.y - _Scale * 0.5;
            // v.vertex.y -= fmod(-v.vertex.y + timer, 4.0 / _Range) + v.vertex.y - _Scale * 0.5;
        }

        struct Input {
            float2 uv_MainTex;
        };

        // サーフェースシェーダー関数
        void surf(Input IN, inout SurfaceOutput o) {
            half4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb * _Albedo.rgb;
            o.Emission = _Emission; 
            o.Specular = _Specular; 
            o.Gloss = _Gloss; 
            o.Alpha = _Albedo.a; 
        }
        ENDCG
    }
}

以上でシェーダーの編集は完了となります。  

補足: シェーダーのファイル名について

Unity上でのシェーダーの名前表示はRainですが、実際のファイル名はRain.shaderになります。

シェーダーファイルの名前の確認

シェーダーファイルの名前の確認

Projectウィンドウの下を見るとファイル名を確認できます。  

マテリアルの作成

次にマテリアルを作成するまでの手順を解説します。

シェーダーからマテリアル作成

シェーダーからマテリアル作成

  シェーダーRainを右クリックし、Create/Materialを選択します。

マテリアル作成

マテリアル作成

  Custom_Rainという名前のマテリアルが作成されます

作成されたマテリアルの確認

作成されたマテリアルの確認

以上で頂点を揺らすためのファイル作成は完了となります  

マテリアルを試しに使ってみる

作成したマテリアルCustom_Rainを、シーン上の適当な3Dモデルにくっつけてみまっしょう

マテリアルをオブジェクトにドラッグアンドドロップ

マテリアルをオブジェクトにドラッグアンドドロップ

  3Dモデルにマテリアルをくっつけると以下のような動きになります。

マテリアル追加後の動き

マテリアル追加後の動き

マテリアルCustom_Rainはモデルの頂点を動かしているため、このようなゆらゆらとした動きになります。  

マテリアルの設定の確認

マテリアルをクリックすると、Inspectorウィンドウにマテリアルの設定数値が表示されます。

マテリアルの設定項目

マテリアルの設定項目

ここにある数値を変更することで、マテリアルの挙動を変えることができます。

 

雨に動きを与える

マテリアルCustom_RainをRainオブジェクトにくっつけると、雨のような動きをするようになります。

三角形メッシュとマテリアルを組み合わせて雨を作る

三角形メッシュとマテリアルを組み合わせて雨を作る

  ここでは、三角形メッシュにマテリアルをくっつけて動きをつける手順を解説します。   まずは雨のPrefabを選択し、Materialsの右にある〇ポチをクリックします。

雨のPrefabへのマテリアル登録

雨のPrefabへのマテリアル登録

  マテリアル一覧の中から先ほど作成したマテリアルCustom_Rainを選択します。

マテリアルCustom_Rainを選択

マテリアルCustom_Rainを選択

  マテリアルCustom_Rainを設定すると動きが与えられて雨のような動きをするようになります。

マテリアル登録後の三角形メッシュ

マテリアル登録後の三角形メッシュ

 

雨の見た目の調整

雨の太さや色を調整して、より雨らしい見た目へと調整していきます。   まずはマテリアルCustom_Rainを選択し、色の入力欄をクリックします。

マテリアルの色の入力部をクリック

マテリアルの色の入力部をクリック

  色のウィンドウが出てくるので、 以下のように設定し、雨を青っぽい半透明な色にします

水色を設定

水色を設定

 

カラーウィンドウの詳細

カラーウィンドウの詳細

  設定すると以下のような見た目になります。

色を変更した後の雨

色を変更した後の雨

 

形の調整(雨粒の大きさや数など)

雨のPrefabの設定を変更することで、雨粒の大きさや雨粒の数などを変更することができます。

Prefabでの雨の設定

Prefabでの雨の設定

 

雨粒を小さくしてみる

雨粒の大きさの部分の数値を変えると雨粒の大きさを変えることができます。

雨粒の大きさの変更

雨粒の大きさの変更

  大きさを0.1にした場合、雨は以下のような見た目になります。

雨粒の大きさ変更後

雨粒の大きさ変更後

雨の数を増やしてみる

雨粒の数を増やすと、濃い雨になります。

雨粒の数の変更

雨粒の数の変更

  雨粒の数を6000に設定すると、以下のような見た目になります。

雨粒の数の変更後

雨粒の数の変更後

 

雨をバラつかせてみる

雨全体のバラつきを増やすと雨がバラつくようになります。

雨全体のバラつきの変更

雨全体のバラつきの変更

  雨全体のバラつきに(16,4,16)を設定すると、以下のような見た目になります。

雨粒のバラつきの変更後

雨粒のバラつきの変更後

 

STYLYへアップロード

雨のPrefabを右クリックし、「Upload prefab to STYLY」を選択。

STYLYへPrefabをアップロード

STYLYへPrefabをアップロード

アップロードが始まるので、気長に待ちます。   Upload Succeededと出ればアップロード完了です。

アップロード完了時に出現するウィンドウ

アップロード完了時に出現するウィンドウ

 

STYLY上でMyModelsの中を見ると、アップロードしたRainがあります。

アップロードしたPrefab

アップロードしたPrefab

  STYLYに雨を降らせましょう

STYLYにRainを配置

STYLYにRainを配置

  ※サンプルプロジェクトはこちらから:https://github.com/styly-dev/STYLY-Unity-Examples/tree/master/Assets/STYLY_Examples/Rain