【Unity】サクッとメンガーのスポンジを作図する

Cluster,Inc. Advent Calendar 2018、25日目の記事です。 qiita.com

概要

そろそろアドベントカレンダーのネタが切れてきたのたぐす(notargs)です。

フラクタルの勉強がしたかったので、Unityの簡単な機能だけを使ってメンガーのスポンジを作図してみました。

f:id:notargs:20181221162341p:plain
メンガーのスポンジ

メンガーのスポンジ - Wikipedia

並べる

まず、縦横奥方向に並べてる処理を書いてみます。

大まかな流れとしては

  • 並べる図形を用意する
  • 縦横奥方向に3つずつ並べる
  • XYZ平面のうち、どれかの中心に位置していたらキャンセルする(置かない)

といった感じのアルゴリズムになっています。

この処理を何度も繰り返すことで、フラクタル図形に近似していきます。

using UnityEngine;

public class Sponge : MonoBehaviour
{
    void Start()
    {
        // 元となるCube(Prefab?)を作る
        // シーンに置きっぱなしなので厳密にはPrefabではない
        var prefab = GameObject.CreatePrimitive(PrimitiveType.Cube);

        // 縦横奥方向に3つずつ並べる
        for (var x = 0; x < 3; ++x)
        {
            for (var y = 0; y < 3; ++y)
            {
                for (var z = 0; z < 3; ++z)
                {
                    // 真ん中に穴を開ける(XYZ平面のうち、どれかが真ん中だったらキャンセルする)
                    if (x == 1 && y == 1 ||
                        x == 1 && z == 1 ||
                        y == 1 && z == 1)
                    {
                        continue;
                    }
                
                    // Prefab?を複製する
                    var obj = Instantiate(prefab);
                    obj.transform.position = new Vector3(x, y, z);
        
                }
            }
        }
        
        // シーンに置いてあったPrefabを削除する
        Destroy(prefab);
    }
}

まだフラクタルではないですが、メンガーのスポンジになりそうな雰囲気はあります。

f:id:notargs:20181221170019g:plain

繰り返す

続いて、さっき作ったものを1/3のサイズに縮小して、再度複製してみます。

using UnityEngine;

public class Sponge : MonoBehaviour
{
    
    void Start()
    {
        // 元となるCube(Prefab?)を作る
        // シーンに置きっぱなしなので厳密にはPrefabではない
        var prefab = GameObject.CreatePrimitive(PrimitiveType.Cube);
        var parent = new GameObject();

        for (var i = 0; i < 2; ++i)
        {
            // 縦横奥方向に3つずつ並べる
            for (var x = 0; x < 3; ++x)
            {
                for (var y = 0; y < 3; ++y)
                {
                    for (var z = 0; z < 3; ++z)
                    {
                        // 真ん中に穴を開ける(XYZ平面のうち、どれかが真ん中だったらキャンセルする)
                        if (x == 1 && y == 1 ||
                            x == 1 && z == 1 ||
                            y == 1 && z == 1)
                        {
                            continue;
                        }

                        // Prefab?を複製する
                        var obj = Instantiate(prefab, parent.transform, true);
                        obj.transform.position = new Vector3(x, y, z);

                    }
                }
            }
            
            // Prefab?を削除
            Destroy(prefab);
            
            // 1/3に縮小したものをPrefabに差し替え、次のループに移行する
            parent.transform.localScale = Vector3.one / 3.0f;
            prefab = parent;
            parent = new GameObject();
        }
        
        // 後処理
        Destroy(parent);
    }
}

ちょっとメンガーのスポンジっぽくなりました!

f:id:notargs:20181221173011p:plain

イテレーションを増やす

更に、繰り返し回数を増やしていくとメンガーのスポンジっぽさが増していきます。

f:id:notargs:20181221173159p:plain
1回

f:id:notargs:20181221173011p:plain
2回

f:id:notargs:20181221173203p:plain
3回

f:id:notargs:20181221173207p:plain
4回

応用

複製する際に、XYZ軸に対して回転するような処理を入れても、また違った面白い図形が出てきます。

f:id:notargs:20181221173730p:plain

using UnityEngine;

public class Sponge : MonoBehaviour
{
    
    void Start()
    {
        // 元となるCube(Prefab?)を作る
        // シーンに置きっぱなしなので厳密にはPrefabではない
        var prefab = GameObject.CreatePrimitive(PrimitiveType.Cube);
        var parent = new GameObject();

        for (var i = 0; i < 4; ++i)
        {
            // 縦横奥方向に3つずつ並べる
            for (var x = 0; x < 3; ++x)
            {
                for (var y = 0; y < 3; ++y)
                {
                    for (var z = 0; z < 3; ++z)
                    {
                        // 真ん中に穴を開ける(XYZ平面のうち、どれかが真ん中だったらキャンセルする)
                        if (x == 1 && y == 1 ||
                            x == 1 && z == 1 ||
                            y == 1 && z == 1)
                        {
                            continue;
                        }

                        // Prefab?を複製する
                        var obj = Instantiate(prefab, parent.transform, true);
                        obj.transform.position = new Vector3(x, y, z);
                        obj.transform.rotation = Quaternion.Euler(45, 45, 45);
                    }
                }
            }
            
            // Prefab?を削除
            Destroy(prefab);
            
            // 1/3に縮小したものをPrefabに差し替え、次のループに移行する
            parent.transform.localScale = Vector3.one / 3.0f;
            prefab = parent;
            parent = new GameObject();
        }
        
        // 後処理
        Destroy(parent);
    }
}

まとめ

図形だけを見ると複雑そうに見えますが、意外と簡単に描画することができます。

また、複製のしかたを工夫することで、オリジナルのフラクタルを作り出すこともできます。

今回は普通のMeshでやりましたが、RayMarchingなどのテクニックを組み合わせることでより面白い図形が描画できたりするので、機会があったらそれについても解説しようと思います。