UnityでMeshをスクリプトから作る

はじめに

3DCGは基本的には三角形の集合体であるポリゴンがベースで作られています。 そのため3DCGの理解にはポリゴンの理解が必要になってくるでしょう。 この記事では、Unityでスクリプトからポリゴンを作成する方法を紹介します。

実行結果

Unityのバージョン: 2018.2.5f1

三角形のMeshを作る

まずは一番単純な三角形を作ってみましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateTriangleMesh : MonoBehaviour {

  void Start () {
    var mesh = new Mesh ();

    var vertices = new List<Vector3> {
      new Vector3 (-1, 0, 0),
      new Vector3 (0, 1, 0),
      new Vector3 (1, 0, 0),
    };
    mesh.SetVertices (vertices);

    var triangles = new List<int> { 0, 1, 2 };
    mesh.SetTriangles (triangles, 0);
  }

}

Meshクラスが、Unityでのポリゴンを扱うクラスとなっています。 まずはじめにMeshクラスのインスタンスを作っています。 Meshクラスの公式ドキュメントはこちらです。

三角形を作るには、頂点の座標と頂点の結び方の2つの情報を与えます。

三角形の情報

最初にMeshのインスタンスに対して三角形の頂点の位置の座標を与えます。 三角形の頂点の位置情報をVector3Listで用意し、 作成したMeshクラスのインスタンスにSetVerticesで渡しています。

次に頂点を結ぶ順番を指定します。 三角形の0番始まりの頂点のインデックスを並べることで頂点を結ぶ順番を指定します。 int型のListで頂点を結ぶ順番にインデックスを並べ、SetTrianglesに渡します。 SetTrianglesの2つ目の引数のsubmeshはサブメッシュのインデックスを指定するものです。 サブメッシュはひとつのメッシュに対して複数のマテリアルを割り振る場合に使われるようです。 今回はマテリアルをひとつしか使わないので、サブメッシュの0番を設定します。

これで最低限のポリゴンを作成するコードが完成しました。

Meshを表示する

ここまでのコードでMeshを作成することはできましたが、 このままではこのMeshは画面に表示されていません。 Meshを表示する方法はいくつかありますが、 ここでは一番簡単なMeshRendererとMeshFilterを使った方法を試してみます。

空のGameObjectに先のスクリプトとMeshRenderer、MeshFilterを追加します。

スクリプトとMeshRenderer、MeshFilterを追加

そしてStartの最後に次のコードを付け加えます。

var meshFilter = GetComponent<MeshFilter> ();
meshFilter.mesh = mesh;

MeshRendererはMeshFilterにセットされたMeshをレンダリングする作業をやってくれます。 MeshFilterのmeshプロパティに作成したMeshを渡すことで MeshRendererによってMeshがレンダリングされました。

実行結果

MeshRendererのマテリアルをStandardシェーダーのものに変更すると 次のようになります。

実行結果

ポリゴンの表裏

慣れないと奇妙に思えるかもしれませんが、ポリゴンには表と裏の概念があります。

きちんと閉じた立体ならば、ポリゴンの裏面が表面に出てくることはありません。 そのため、3DCGでは裏を向いたポリゴンの描画を省略することで 描画を効率化することがよくあります。

ちなみに、このきちんと閉じているポリゴンは「2次元多様体」とか言ったりするそうです。 2次元多様体はすべてのエッジに対して接続する面が2つ存在します。

閉じた図形は裏面が見えることがないので裏面の描画を省略する場合がある

試しに先ほど作成したポリゴンの裏側へ回ってみます。

裏側へ回ってみる

裏側へ回るとポリゴンがレンダリングされなくなりました。

ポリゴンの表と裏は頂点の順番で指定をします。 Unityでは、三角形の表面の法線ベクトルの方向を向いたとき 反時計回りになる順番で頂点を結びます。 別の表現をすると、左手の親指を面の法線方向に向けたとき 残りの指が曲がる方向へ順に頂点を結びます。

Unityのポリゴンは左回転

試しにさきほどのコードを書き換えて頂点を結ぶ順番を逆にしてみましょう。

var triangles = new List<int> { 2, 1, 0 };
mesh.SetTriangles (triangles, 0);

こうすることで面の向きが逆になることが確認できます。

逆回転のポリゴン

verticesプロパティ

Meshクラスにはverticesプロパティがあります。 上で使ったSetVerticesの代わりにこのプロパティを使うこともできます。 昔はSetVerticesが存在せず、verticesを使うしかなかったようです。

var vertices = new Vector3[] {
  new Vector3 (-1, 0, 0),
  new Vector3 (0, 1, 0),
  new Vector3 (1, 0, 0),
};
mesh.vertices = vertices;

一見すると、verticesMeshの頂点をフィールドのように見えますが、 実態はプロパティとなっています。 verticesはgetするたびに新しい配列を作り直しています。 verticesをgetしてそれに変更を加えてももとのMeshには影響がありません。

var vertices = mesh.vertices;
vertices[0] = new Vector3(0, 0, 0); // これだけではMeshは変更されない
mesh.vertices = vertices; // `mesh.vertices`に代入して初めて変更される

フィールドのように見えるのに実際は新しい配列を作り直している、というのがなかなかの罠です。 今はSetVerticesGetVerticesが存在するので、 そちらを優先して使ったほうが読みやすいコードになるでしょう。

四角形のMeshを作る

次は四角形を作ってみましょう。 今回は次のような三角形2つで作られた四角形のMeshを作ります。

三角形2つで構成された四角形

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateQuadMesh : MonoBehaviour {

  void Start () {
    var mesh = new Mesh ();

    var vertices = new List<Vector3> {
      new Vector3 (-1, -1, 0),
      new Vector3 (-1, 1, 0),
      new Vector3 (1, 1, 0),
      new Vector3 (1, -1, 0),
    };
    mesh.SetVertices (vertices);

    var triangles = new List<int> { 0, 1, 2, 2, 3, 0 };
    mesh.SetTriangles (triangles, 0);

    var meshFilter = GetComponent<MeshFilter> ();
    meshFilter.mesh = mesh;
  }

}

実行結果

三角形ふたつを作るのに頂点は6つ必要ですが、そのうち2つをそれぞれの三角形で共有することで 必要な頂点の数を4つに減らしています。

同じ四角形を次のように頂点を6つにすることもできます。

void Start () {
  var mesh = new Mesh ();

  var vertices = new List<Vector3> {
    new Vector3 (-1, -1, 0),
    new Vector3 (-1, 1, 0),
    new Vector3 (1, 1, 0),

    new Vector3 (1, 1, 0),
    new Vector3 (1, -1, 0),
    new Vector3 (-1, -1, 0),
  };
  mesh.SetVertices (vertices);

  var triangles = new List<int> { 0, 1, 2, 3, 4, 5 };
  mesh.SetTriangles (triangles, 0);

  var meshFilter = GetComponent<MeshFilter> ();
  meshFilter.mesh = mesh;
}

しかし、これでは重複する頂点の分だけ無駄になってしまいます。 今は頂点の座標情報しか持っていないので、頂点の数が多少増えても気になりませんが、 実際のポリゴンでは頂点ごとに法線やカラーの情報、UVの情報などのさまざまな情報が存在します。 同じ頂点を重複させるとそれだけ無駄ができてしまうので、 同じ頂点の情報は1つにまとめて面の貼り方をインデックスで与えます。

頂点カラーを設定する

頂点に座標以外の情報も渡してみましょう。 まずは頂点カラーを渡してみます。

var colors = new List<Color> {
  Color.blue,
  Color.red,
  Color.green,
  Color.magenta
};
mesh.SetColors (colors);

頂点の座標と同じように頂点カラーを渡すメソッドを使います。 Color型のListを用意して渡します。

この頂点カラーのような頂点ごとの情報を頂点属性(vertex attribute)と呼んだりします。

渡した頂点カラーは頂点カラーに対応したマテリアルだと画面に反映されます。 Standardシェーダのマテリアルでは頂点カラーは表示されません。

実行結果

頂点カラーを扱うマテリアルの作成については、後日また別の記事にまとめます。

(2018/08/29追記: 頂点カラーを扱うマテリアルについての記事書きました)

UV座標を指定する

もう一種類、頂点属性として今度はUV座標を渡してみましょう。

var uvs = new List<Vector2> {
  new Vector2 (0, 0),
  new Vector2 (0, 1),
  new Vector2 (1, 1),
  new Vector2 (1, 0),
};
mesh.SetUVs (0, uvs);

UVには複数のチャンネルがあります。 通常のテクスチャとディティール用のテクスチャでUVを2つ使うような場合に利用します。 今回は一番最初のチャンネルを指定しています。

今回は次のようなUV座標の情報を渡しています。

UV座標

マテリアルのテクスチャに適当な画像を指定してみます。

テクスチャ

テクスチャマッピング

きちんとテクスチャマッピングされることが確認できました。

UVにはVector3やVector4まで渡せます。

var uvs = new List<Vector4> {
  new Vector4 (0, 0, 0, 0),
  new Vector4 (0, 1, 0, 0),
  new Vector4 (1, 1, 0, 0),
  new Vector4 (1, 0, 0, 0),
};
mesh.SetUVs (0, uvs);

3番目や4番目の値はテクスチャマッピングには使われません。 空いているUVチャンネルや、Vectorの3番目や4番目などに頂点ごとの情報を詰め込んで シェーダで利用したりといった応用に使えます。

おわりに

今回はスクリプトから簡単なポリゴンを作成してみました。

ソースコードはこちらのリポジトリにおいておきます。

  • Unity
新しい投稿
記事の更新方針