読者です 読者をやめる 読者になる 読者になる

Onoty3D

Unityに関するメモとか

プロ生ちゃんのMeshを切り分ける

Unity プロ生ちゃん Cloth

プロ生ちゃん Advent Calendar 2015 9日目の記事です。
qiita.com

以前、Unity5のClothを使って、プロ生ちゃんのスカートをClothシミュレーションする記事を書きました。
onoty3d.hatenablog.com
onoty3d.hatenablog.com

また、Twitter上でプロ生ちゃんが服だけ残してドロンと消える、なんてネタもやりました。

Clothデータの作成は、Meshがはじめからパーツ単位で分かれているモデル(ユニティちゃん等)では楽ですが、プロ生ちゃんモデルはMeshが一つになっているので、別途パーツ単位でMeshを切り分ける必要があります。
プロ生ちゃんに関してはBlender、FBX、PMXデータが公開されているので、3Dモデルが編集できるツールを使えば、切り分けは可能かと思います。
PMXデータからMeshを切り分ける方法は、以下記事で紹介しました。
onoty3d.hatenablog.com

毎度外部ツールを使うのも面倒なので、今回Unityエディタ上でSubMesh単位でMeshが出力できるスクリプトを作成しました(ソースは最後)。
プロ生ちゃん以外にもSkinnedMeshRendererないしMeshFilterを持つオブジェクトから、SubMeshを切り分けできます。
(Meshの任意の部分を切り分けるのではなく、SubMesh単位で切り分けするだけです。)

使い方
1.スクリプトをEditorフォルダ配下に配置すると、UnityエディタのメニューにCustomが増えます。
2.シーン上にプロ生ちゃんモデルを配置して、Hierarchyビューでプロ生ちゃんを選択し、メニューから
Custom>Export>ExportSubMesh(from SkinnedMeshRenderer)を選ぶと、Mesh単位でMeshが出力されます。
f:id:onoty3d:20151208111536p:plain
複数のオブジェクトを選択していた場合でも、最初のオブジェクのみ処理の対象とします。
※対象オブジェクトに複数のSkinnedMeshRenderer/MeshFilterが適用されている場合でも、最初に見つかったそれからのみSubMeshの切り分けをします。
※出力フォルダは、Assets配下の「選択オブジェクト名+SubSubMeshes」となります。またファイル名は「submesh+連番」となります。
f:id:onoty3d:20151208111548p:plain
これでClothシミュレーションがお手軽になるかと思います。
※高ポリゴンモデルのサブメッシュを分割しようとした場合、マシンスペックにもよると思いますが、非常に時間がかかります。ご注意ください。
プロ生ちゃんモデルでも自分のPCでは10秒ほどかかります。

Clothシミュレーションは、以下記事がとても丁寧で参考になります。
blogs.unity3d.com


以下ソース
【追記:2015/12/13】動いているのかわかりづらいので、プログレスバーの表示を追加しました。
f:id:onoty3d:20151213112429p:plain

using System.Linq;
using UnityEditor;
using UnityEngine;

public class SubMeshExporter : ScriptableObject
{
    // Use this for initialization
    private void Start()
    {
    }

    // Update is called once per frame
    private void Update()
    {
    }

    [MenuItem("Custom/Export/ExportSubMesh(from SkinnedMeshRenderer)")]
    private static void ExportSubMeshFromSkinnedMeshRenderer()
    {
        //何も選択されていなかったら終了
        if (Selection.gameObjects.Length == 0) return;

        var gameObject = Selection.gameObjects[0];
        var skinnedMeshRenderer = gameObject.GetComponentInChildren<SkinnedMeshRenderer>();

        //選択したオブジェクトにSkinnedMeshRendererが無かったら終了
        if (skinnedMeshRenderer == null) return;

        //出力
        SubMeshExporter.ExportSubMesh(gameObject.name, skinnedMeshRenderer.sharedMesh);
    }

    [MenuItem("Custom/Export/ExportSubMesh(from MeshFilter)")]
    private static void ExportSubMeshFromMeshFilter()
    {
        //何も選択されていなかったら終了
        if (Selection.gameObjects.Length == 0) return;

        var gameObject = Selection.gameObjects[0];
        var meshFilter = gameObject.GetComponentInChildren<MeshFilter>();

        //選択したオブジェクトにMeshFilterが無かったら終了
        if (meshFilter == null) return;

        //出力
        SubMeshExporter.ExportSubMesh(gameObject.name, meshFilter.sharedMesh);
    }

    private static void ExportSubMesh(string name, Mesh mesh)
    {
        //フォルダ名の決定
        var guid = AssetDatabase.CreateFolder("Assets", name + "SubMeshes");
        var folderName = AssetDatabase.GUIDToAssetPath(guid);

        //サブメッシュの出力
        var title = "Export";
        var info = "Exporting subMesh {0}/{1} ...";
        try
        {
            for (int index = 0; index < mesh.subMeshCount; index++)
            {
                EditorUtility.DisplayProgressBar(title, string.Format(info, index + 1, mesh.subMeshCount), (float)(index + 1) / (float)mesh.subMeshCount);
                SubMeshExporter.CreateMesh(folderName, mesh, index);
            }
        }
        finally
        {
            EditorUtility.ClearProgressBar();
        }
    }

    private static void CreateMesh(string folderName, Mesh parent, int index)
    {
        //サブメッシュの三角形リスト取得
        var triangles = parent.GetTriangles(index);

        //サブメッシュの三角形リスト(同値省く)取得
        var trianglesUnique = triangles.Distinct().ToList();

        //サブメッシュの頂点位置とテクスチャ座標の切り出し
        var vertices = trianglesUnique.Select(x => parent.vertices[x]).ToArray();
        var uv = trianglesUnique.Select(x => parent.uv[x]).ToArray();

        //三角形リストの値をサブメッシュに合わせてシフト
        var triangleNewIndexes = trianglesUnique.Select((x, i) => new { OldIndex = x, NewIndex = i }).ToDictionary(x => x.OldIndex, x => x.NewIndex);
        var triangleConv = triangles.Select(x => triangleNewIndexes[x]).ToArray();

        //メッシュ作成
        var mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.uv = uv;
        mesh.triangles = triangleConv;

        //バウンディングボリュームと法線の再計算
        mesh.RecalculateBounds();
        mesh.RecalculateNormals();

        //出力
        AssetDatabase.CreateAsset(mesh, folderName + "/submesh" + index + ".asset");
    }
}