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
のインスタンスに対して三角形の頂点の位置の座標を与えます。
三角形の頂点の位置情報をVector3
のList
で用意し、
作成したMesh
クラスのインスタンスにSetVertices
で渡しています。
次に頂点を結ぶ順番を指定します。
三角形の0番始まりの頂点のインデックスを並べることで頂点を結ぶ順番を指定します。
int
型のList
で頂点を結ぶ順番にインデックスを並べ、SetTriangles
に渡します。
SetTriangles
の2つ目の引数のsubmesh
はサブメッシュのインデックスを指定するものです。
サブメッシュはひとつのメッシュに対して複数のマテリアルを割り振る場合に使われるようです。
今回はマテリアルをひとつしか使わないので、サブメッシュの0番を設定します。
これで最低限のポリゴンを作成するコードが完成しました。
Meshを表示する
ここまでのコードでMeshを作成することはできましたが、 このままではこのMeshは画面に表示されていません。 Meshを表示する方法はいくつかありますが、 ここでは一番簡単なMeshRendererとMeshFilterを使った方法を試してみます。
空のGameObjectに先のスクリプトと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では、三角形の表面の法線ベクトルの方向を向いたとき 反時計回りになる順番で頂点を結びます。 別の表現をすると、左手の親指を面の法線方向に向けたとき 残りの指が曲がる方向へ順に頂点を結びます。
試しにさきほどのコードを書き換えて頂点を結ぶ順番を逆にしてみましょう。
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;
一見すると、vertices
はMesh
の頂点をフィールドのように見えますが、
実態はプロパティとなっています。
vertices
はgetするたびに新しい配列を作り直しています。
vertices
をgetしてそれに変更を加えてももとのMesh
には影響がありません。
var vertices = mesh.vertices;
vertices[0] = new Vector3(0, 0, 0); // これだけではMeshは変更されない
mesh.vertices = vertices; // `mesh.vertices`に代入して初めて変更される
フィールドのように見えるのに実際は新しい配列を作り直している、というのがなかなかの罠です。
今はSetVertices
とGetVertices
が存在するので、
そちらを優先して使ったほうが読みやすいコードになるでしょう。
四角形のMeshを作る
次は四角形を作ってみましょう。
今回は次のような三角形2つで作られた四角形のMesh
を作ります。
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には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番目などに頂点ごとの情報を詰め込んで シェーダで利用したりといった応用に使えます。
おわりに
今回はスクリプトから簡単なポリゴンを作成してみました。
ソースコードはこちらのリポジトリにおいておきます。