2021年2月11日木曜日

Unityで3D平面をスクリプトから変形する

Unityで既存の平面オブジェクト(3D Object/Plane)にアタッチして起動するとそのオブジェクトが2次曲面に変形するスクリプトのメモ. 数式で表される任意の形状のオブジェクトを作る練習. メニューにスクリプトを登録して配置する方法だと後で形状やポリゴン数を変えることができない. 逆にこの例では実行時に毎フレームぐにゃぐにゃと形状を変えるサンプルプログラム.

使い方

以下のサンプルプログラムはすべてMonoBehaviourを継承したクラスないのメンバ. このクラスを平面オブジェクトにアタッチして使う. Start関数内にあるDefrom関数がアタッチしたオブジェクトの変形プログラム. ここではa,bは二次曲面の二次の係数(主曲率). Update内で時間とともにこのa,bが振動して変化するようにしてるけど,普通は一度呼び出すだけでいい.

//分割数(ポリゴン数=128x128x2)
public int n_divx = 128;
public int n_divz = 128;

//サイズ
public float size_x = 1f;
public float size_z = 1f;

void Start()
{
    float a = 0.8f;
    float b = 0.1f;
    Deform(a, b);
    t_0 = DateTime.Now;
}
void Update(){
    TimeSpan  t = DateTime.Now - t_0;
    double omega = 0.01;
    double a = 0.8*Math.Sin(omega*t.TotalMilliseconds);
    double b = 0.8*Math.Sin(0.5*omega*t.TotalMilliseconds);
    Deform((float)a, (float)b);
}

メッシュの変形

肝心のメッシュ変形の核心部分. 要するにMeshオブジェクトを新規作成して,既存のオブジェクトのsharedMeshに代入して置き換えているだけ. 頂点や三角形ポリゴンの指定方法がマニュアルからでは分かりにくいのでサンプルがあるとありがたい. 頂点は平面をn_divx$\times$n_divz個の矩形に分割しているので, 頂点の数としては(n_divx+1)$\times$(n_divz+1)になる. ここではx,zのグリッドを基準にy方向に二次関数値を代入している.

三角形ポリゴンに指定法法は,コメントに描いた図のとおり, 各矩形が横に一列並んでいることを想像すると理解しやすい. 点Aから点B,その横と順に数えていき,右端に達したら 左端一段下に戻り点C,点D,その右横という順にインデクスがふられている. このうち各矩形をさらに2つの三角形ACDとBADに分割して各頂点のインデックスが登録されている.

void Deform(float a, float b)
{
    MeshFilter meshFilter = this.GetComponent<MeshFilter>();
    MeshRenderer meshRenderer = this.GetComponent<MeshRenderer>();

    //頂点
    int n_vertx = n_divx + 1;
    int n_vertz = n_divz + 1;
    Vector3[] vertices = new Vector3[n_vertx * n_vertz]; // 頂点
    Vector2[] uv = new Vector2[n_vertx * n_vertz]; // uv座標
    int k = 0;
    for (int i = 0; i <= n_divz; i++)
    {
        for (int j = 0; j <= n_divx; j++)
        {
            float u = (float)j / n_divx;
            float v = (float)i / n_divz;
            float x = (u - 0.5f) * size_x;
            float z = (v - 0.5f) * size_z;
            float y = a*x*x + b*z*z;
            vertices[k] = new Vector3(x, z, y);
            uv[k++].Set(u, v);
        }
    }

    //三角形ポリゴン
    // (A=0, B=1, C=n_vertx, D=n_verty)
    // A-------B-----
    // | \     |
    // |  \  2 |
    // |   \   |
    // | 1  \  |
    // |     \ |
    // C-------D-----
    var triangles = new int[6 * n_divx * n_divz];
    int l = 0, A = 0, B = 1, C = n_vertx, D = n_vertx + 1;
    for (int i = 0; i < n_divz; i++)
    {
        for (int j = 0; j < n_divx; j++)
        {
            //1
            triangles[l++] = A;
            triangles[l++] = C++;
            triangles[l++] = D;
            //2
            triangles[l++] = B++;
            triangles[l++] = A++;
            triangles[l++] = D++;
        }
        A++; B++; C++; D++;
    }

    //メッシュ生成
    Mesh mesh = new Mesh();
    mesh.vertices = vertices;
    mesh.triangles = triangles;
    mesh.uv = uv;
    meshFilter.sharedMesh = mesh;

    // 法線とバウンディングボックスを再計算
    mesh.RecalculateNormals(); 
    mesh.RecalculateBounds();
    this.GetComponent<MeshCollider>().sharedMesh = mesh;
}