プログラミングで世界を変える

ゲームプログラミングと技術のこと

Unityで動的に布モデルの切断をしようと頑張ったけど無理だった話。

今回実現したかった、リアルタイムに布シミュレーションをしながらメッシュを動的に変化させることは、できませんでした。とはいえ時間をかけて出来なかったこと結論付けたので、備忘録的な意味と、あわよくば詳しい人が解決策を提示してくれることを期待して、この記事を書きました。

ソースコードの全文もアップしておいたので、詳しい人教えてください!

リアルタイムに布シミュレーションをしながらメッシュを動的に変化させるスクリプト · GitHub

はじめに

まず、Unity標準で用意されている布シミュレーションを使おうと考えました。 布シミュレーションについてはこちらの記事で紹介されています。

blogs.unity3d.com

布シミュレーションは「Cloth」と「SkinnedMeshRenderer」コンポーネントによって動作します。

切断系のアセットは幾つか存在しているのですが、SkinnedMeshRendererに対応しているものが見つからず、どうしようかと考えていた時にこんな記事を見つけました。

esakun.hateblo.jp

Unityでスクリプトからメッシュを生成できるということで、こんなロードマップを描いて検証をすることにしました。

  1. メッシュを生成
  2. SkinndedMeshRendererにメッシュ情報を渡し、シミュレーションを適用する
  3. 切断実行時にメッシュを再構成し、SkinnedMeshRendererに反映させる。

検証

1. メッシュを生成

上記ページを参考にしながらメッシュの生成をします。 まずは三角ポリゴンを二つ組み合わせて四角形を作ります。

       filter.sharedMesh.vertices = new Vector3[] {
            new Vector3 (0, 0, 1f),
            new Vector3 (1f, 0, -1f),
            new Vector3 (-1f, 0, -1f),
            new Vector3 (-1f, 0, -1.2f),
            new Vector3 (-2f, 0, 1f),
        };
        filter.sharedMesh.triangles = new int[] {
            0, 1, 2,
            0, 3, 4,
        };

OpenGLとかの要領でシンプルな図形の描画を確認できました。 次に、大きさを指定して生成できるように拡張します。

       for (int h = 0; h < height; ++h) {
            for (int w = 0; w < width; ++w) {

                vertices.Add (new Vector3 ((1f + w) * size, 0, (1f + h) * size));
                vertices.Add (new Vector3 ((1f + w) * size, 0, (0f + h) * size));
                vertices.Add (new Vector3 ((0f + w) * size, 0, (0f + h) * size));
                vertices.Add (new Vector3 ((0f + w) * size, 0, (1f + h) * size));

                List<int> triangles = new List<int> ();

                triangles.Add (0 + (w + (h * width)) * 4);
                triangles.Add (1 + (w + (h * width)) * 4);
                triangles.Add (2 + (w + (h * width)) * 4);
                triangles.Add (0 + (w + (h * width)) * 4);
                triangles.Add (2 + (w + (h * width)) * 4);
                triangles.Add (3 + (w + (h * width)) * 4);

                // 反対側からの描画にも対応
                triangles = DuplicateReverseTriangles (triangles);

                squares.Add (new Square (h, w, triangles));
            }
        }

Square型は各四角形ごとの順番情報を持ちます。 これは、後々各四角形毎に変形を適用するため、データを扱いやすくする目的があります。 頂点に関してはインデックスで順番を指定する関係上、すべて一つのリストに追加する形で保持します。

2. SkinndedMeshRendererにメッシュ情報を渡し、シミュレーションを適用する

       meshRenderer.sharedMesh = mesh;

SkinndedMeshRendererにメッシュ情報を渡します。

[RequireComponent(typeof(SkinnedMeshRenderer))]
[RequireComponent(typeof(Cloth))]
public class DynamicCreateMesh : MonoBehaviour
{
...
}

クラス宣言の前にこんな記述をしておくと、コンポーネントのアタッチ時に自動的に必要なコンポーネントをアタッチしてくれるので便利です。

必要なコンポーネントを確認して、再生ボタンで実行できます。 うまく動いていることを確認するため、ShereColliderをつけておくと便利だと思います。

以下の記事で手順が丁寧に説明されています。

Interactive Clothに代わる、布っぽい動きを実現するClothを触り始める

3. 切断実行時にメッシュを再構成し、SkinnedMeshRendererに反映させる。

切断の方法を考えます。 各四角形に対して切断面との交点に頂点を追加し、頂点を結ぶ順番を更新します。

一般化は難しそうだと思ったので、とりあえず反映させる部分の確認を進めます。

   // 指定した四角形を切断
    // 一旦テスト用に単純な分割のみに対応
    public void Cut (int h, int w)
    {
        Square square = GetSquare (h, w);

        int maxIndex = vertices.Count;

        float bottomRate = 0.8f;
        float topRate = 0.8f;
        float interval = 0.05f;

        vertices.Add (new Vector3 ((bottomRate + interval + w) * size, 0, (0f + h) * size));
        vertices.Add (new Vector3 ((topRate + interval + w) * size, 0, (1f + h) * size));
        vertices.Add (new Vector3 ((topRate - interval + w) * size, 0, (1f + h) * size));
        vertices.Add (new Vector3 ((bottomRate - interval + w) * size, 0, (0f + h) * size));

        List<int> triangles = new List<int> ();

        triangles.Add (0 + (w + (h * width)) * 4);
        triangles.Add (1 + (w + (h * width)) * 4);
        triangles.Add (0 + maxIndex);

        triangles.Add (0 + (w + (h * width)) * 4);
        triangles.Add (0 + maxIndex);
        triangles.Add (1 + maxIndex);

        triangles.Add (2 + maxIndex);
        triangles.Add (3 + maxIndex);
        triangles.Add (2 + (w + (h * width)) * 4);

        triangles.Add (2 + maxIndex);
        triangles.Add (2 + (w + (h * width)) * 4);
        triangles.Add (3 + (w + (h * width)) * 4);

        triangles = DuplicateReverseTriangles (triangles);

        square.triangles = triangles;

        UpdateMesh ();
    }

冗長なのは一旦気にしない。

これで縦一列に切断処理を実行してみると…。

ちなみに頂点が完全に一致しているとシミュレーション時に分離しないことを確認したので、interval分だけ離してあげています。

問題

とりあえず一通り動作することは確認できましたが、問題が発生しました。

頂点を追加する方法での問題点

f:id:splas_boomerang:20160306041551g:plain

先ほどのスクリプトによる方法です。 切断実行時に注目してもらうと分かるのですが、座標がリセットされています。 これは全く同じ頂点情報を適用した場合には発生しないため、恐らく頂点数が変化した時に起こる挙動だと考えます。

頂点座標を変更する方法での問題点

ならばと、あらかじめ多めに頂点を生成しておき、必要に応じて頂点座標を切断面との交点に移動させる形式での検証を行いました。

だめそうです。

その他懸念事項

Edit Constraintsのスクリプトによる設定

布をある点において固定する設定です。必要になってくる機能なのですが、基本的にはエディタ上でやる操作らしく、スクリプトから設定する方法がわかっていません。

ClothコンポーネントがClothSkinningCoefficientの配列をメンバとして持っているので、この辺りかと思ったのですが、値をセットしても変化が見られませんでした。

Unity - スクリプトリファレンス: ClothSkinningCoefficient

これに関しては動的に行う必要がないため、エディタ上で設定したのちPrefabとして保存する手もありそうです。

携帯端末での動作確認。

最終的にはスマートフォン向けのアプリケーションに使用するため、諸問題が解決したとして、端末上での動作を確認する必要があります。影が描画されなかったりしますよね。面倒でしてないってだけなんですけど。

余談

Unity 3.0ではこんなことができたみたいです。 5.0で「Interactive Cloth」と「Skinned Cloth」が統合されて「Cloth」になったタイミングで廃止されたみたいですが…。

Cloth tearing in Unity 3.0 beta from brokenpoly on Vimeo.