【連載】Unity時代の3D入門 – 第2回「トーラスを描画してみた」

こんにちは、クライアントサイドエンジニアの矢野です。

連載の第1回では、ポリゴンの描画をしました。
今回は、前回の知識を応用してドーナツ型の立体であるトーラスの描画をしてみます。

立体といっても、新しい知識は必要ありません。
前回のまとめで書いた通り、シンプルな三角形の描画ができれば3Dの世界は作れます。

トーラスの描画

さっそくですがソースコードです。

using System.Collections.Generic;
using UnityEngine;

public class CreateTorus : MonoBehaviour {

    [SerializeField]
    private Material _material;

    private Mesh _mesh;

    void Awake () {
        var r1 = 3.0f;
        var r2 = 1.0f;
        var n = 20;

        _mesh = new Mesh();

        var vertices = new List<Vector3>();
        var triangles = new List<int>();
        var normals = new List<Vector3>();

        // (1) トーラスの計算
        for (int i = 0; i <= n; i++) {
            var phi = Mathf.PI * 2.0f * i / n;
            var tr = Mathf.Cos(phi) * r2;
            var y = Mathf.Sin(phi) * r2;

            for (int j = 0; j <= n; j++) {
                var theta = 2.0f * Mathf.PI * j / n;
                var x = Mathf.Cos(theta) * (r1 + tr);
                var z = Mathf.Sin(theta) * (r1 + tr);

                vertices.Add(new Vector3(x, y, z));
                // (2) 法線の計算
                normals.Add(new Vector3(tr * Mathf.Cos(theta), y, tr * Mathf.Sin(theta)));
            }
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                var count = (n + 1) * j + i;
                // (3) 頂点インデックスを指定
                triangles.Add(count);
                triangles.Add(count + n + 2);
                triangles.Add(count + 1);

                triangles.Add(count);
                triangles.Add(count + n + 1);
                triangles.Add(count + n + 2);
            }
        }

        _mesh.vertices = vertices.ToArray();
        _mesh.triangles = triangles.ToArray();
        _mesh.normals = normals.ToArray();

        _mesh.RecalculateBounds();
    }

    void Update () {
        Graphics.DrawMesh(_mesh, Vector3.zero, Quaternion.identity, _material, 0);
    }
}

これを適当なGameObjectにアタッチして、前回と同様マテリアルを設定すると、

このように描画されます(カメラは適宜調整してください)。

ソースコードの説明

上記のソースコードでは、まず (1) で頂点座標を求めています。 r1 、 r2、 theta(θ)、 phi(φ) を図示すると下図のようになります。

今回は、まず1つ目の for 文で φ の値を一定量ずつ変えながら、o2 から見た半径 r2 の円周上の点を求めています。
この時、 r2 * sinφ がそのまま頂点のy座標となることは上図を見れば自明です。

次にネストされた for 文 で θ の値を変えながら半径 r1 の円周上の点を求めています。
r2 * cosφ を tr としたとき、xz軸上に投影した頂点までの距離は r1 + tr で表せるので、頂点のx座標とz座標はそれぞれ (r1 + tr) * cosθ、(r1 + tr) * sinθ となります。

参考までに頂点を可視化すると、下図のようになります。

また、法線は o2 から頂点方向に伸びる単位ベクトルとなるので、頂点座標から o2 の座標を差し引いたものを正規化して求めます。
それの計算式を整理したものがソースコードの (2) です。

このようにして頂点を取得した後に、trianglesに、すべてのポリゴンに対して外に開けている面が表となるように頂点インデックスを設定してます。
この処理がソースコード上の (3) となります。

メッシュの情報を見る

ここで、作ったメッシュを可視化してみましょう。
まず、Unityを再生した状態で、Sceneビューの左上のShading ModeをWireFrameに切り替えてみます。

すると、各ポリゴンのアウトラインが見えて立体が三角形で構成されていることが確認できます。

次に、変数 _mesh をSerializeFieldにしてインスペクタに表示し、参照ボタンをクリックします。

すると、頂点数やポリゴン数の情報がみれます。

このメッシュは441個の頂点と800個の三角形から形成されていることがわかります。

メッシュの保存

最後に、せっかくUnityでメッシュを生成したので、Unityのアセットとしてメッシュ情報を保存しておきましょう。
保存するためには、最初のソースコードに以下の二つのメソッドを追加します。

    private void OnGUI() {
        if (GUI.Button(new Rect(20, 20, 100, 50), "Save Mesh")){
            SaveMesh();
        }
    }

    private void SaveMesh(){
        UnityEditor.AssetDatabase.CreateAsset (_mesh, "Assets/torus.asset");
        UnityEditor.AssetDatabase.SaveAssets ();
    }

この状態で再生すると画面上にSave Mesh ボタンが現れ、それをクリックするとAssets配下にメッシュが生成されます。

メッシュ情報が保存されていることが確認できました。

まとめ

今回は、以下のことを行いました。

  • 複雑な立体の描画
  • メッシュの情報の見方
  • メッシュの保存の仕方

特に新しい3Dの知識は登場していませんが、トーラスの描画を通して理解はより深まったのではないでしょうか。

次回はこの立体に画像を貼り付けていく予定です。

バックナンバー