Unity上でスクリプトを使って動的にメッシュを生成するには頂点座標と頂点を結ぶトライアングルの理解が必要になります。
私もトライアングルの概念や図形を描くための関数が面倒で拒否感がありました。
しかし、Unity Editor上で視覚的に捉えて力業で覚えることで理解していくことができました。
「1点の座標を中心にキューブを作る」 を例にして解説していきます。
頂点座標を作る
StartPosition
の座標を中心としてキューブを構成する8つの頂点を作ります。
頂点座標が視覚的に表示するためvoid OnDrawGizmos()
関数を使います。
この関数はプレイ状態にしなくてもシーンビュー上でギズモとして表示できるので便利です。
using System.Collections.Generic;
using UnityEngine;
public class GenerateCube : MonoBehaviour
{
[SerializeField] Transform StartPosition;
[SerializeField] Material Material;
[SerializeField] float Width = 1f;
[SerializeField] float Height = 1f;
[SerializeField] float Forward = 1f;
[SerializeField] bool IsDrawGizmo = true;
private List<Vector3> CubePositions()
{
var vertices = new List<Vector3>();
Vector3 startPos = StartPosition.transform.position;
//Vector3 lowerLeft = startPos + new Vector3(Width, Height, Forward) / -2f;
Vector3 lowerLeft = startPos + (Vector3.left * Width) / 2f + (Vector3.down * Height) / 2f + (Vector3.back * Forward) / 2f;
Vector3 lowerRight = lowerLeft + Vector3.right * Width;
Vector3 upperLeft = lowerLeft + Vector3.up * Height;
Vector3 upperRight = lowerRight + Vector3.up * Height;
Vector3 forwardLowerLeft = lowerLeft + Vector3.forward * Forward;
Vector3 forwardLowerRight = lowerRight + Vector3.forward * Forward;
Vector3 forwardUpperLeft = upperLeft + Vector3.forward * Forward;
Vector3 forwardUpperRight = upperRight + Vector3.forward * Forward;
vertices.Add(lowerLeft);
vertices.Add(lowerRight);
vertices.Add(upperLeft);
vertices.Add(upperRight);
vertices.Add(forwardLowerLeft);
vertices.Add(forwardLowerRight);
vertices.Add(forwardUpperLeft);
vertices.Add(forwardUpperRight);
return vertices;
}
private void OnDrawGizmos()
{
if(!IsDrawGizmo || StartPosition == null)
{
return;
}
var vertices = CubePositions();
float size = 0.1f;
foreach(var vertex in vertices)
{
Gizmos.DrawSphere(vertex, size);
}
}
}
最初に左下の座標を決めて、そこを起点としてWidth, Height, Forwardのオフセットをして7点の座標を取ります。
頂点座標からトライアングルを作る
3つの頂点を結ぶ三角形が1つの面となります。四角形の形成には2つの三角形で構成されます。
トライアングルを作るルールとして、int型の頂点配列番号を3つでワンセットとし3の倍数になるようにトライアングル配列を作ります。
三角形の配列順は正面から見て右回りになるようにします。左回りで格納すると裏から見えるメッシュとなります。
プレーンのような平面の場合は右回りと左回りの三角形を作ることもありますが、キューブのように閉じたモデルの場合は裏側から見ることがないので左回りの三角形を作る必要がありません。
1つ目の四角形を右回りの三角形の配列順直すと以下のようになります。
[ 1 , 0 , 2 ] [ 1 , 2 , 3 ]
コメントアウトしているtriangles.AddRange
は左回りの三角形の例です。裏側に作った場合、どういった見た目になるか興味あればコメントアウトを逆にして試してみてください。
private List<int> CubeTriangles()
{
var triangles = new List<int>();
int indexLowerLeft = 0;
int indexLowerRight = 1;
int indexUpperLeft = 2;
int indexUpperRight = 3;
int indexFowardLowerLeft = 4;
int indexFowardLowerRight = 5;
int indexFowardUpperLeft = 6;
int indexFowardUpperRight = 7;
// 正面
//triangles.AddRange(SquareToTriangles(indexLowerRight, indexLowerLeft, indexUpperRight, indexUpperLeft));
triangles.AddRange(SquareToTriangles(indexLowerLeft, indexLowerRight, indexUpperLeft, indexUpperRight));
// 左側
//triangles.AddRange(SquareToTriangles(indexLowerLeft, indexFowardLowerLeft, indexUpperLeft, indexFowardUpperLeft));
triangles.AddRange(SquareToTriangles(indexFowardLowerLeft, indexLowerLeft, indexFowardUpperLeft, indexUpperLeft));
// 右側
//triangles.AddRange(SquareToTriangles(indexFowardLowerRight, indexLowerRight, indexFowardUpperRight, indexUpperRight));
triangles.AddRange(SquareToTriangles(indexLowerRight, indexFowardLowerRight, indexUpperRight, indexFowardUpperRight));
// 上側
//triangles.AddRange(SquareToTriangles(indexUpperRight, indexUpperLeft, indexFowardUpperRight, indexFowardUpperLeft));
triangles.AddRange(SquareToTriangles(indexUpperLeft, indexUpperRight, indexFowardUpperLeft, indexFowardUpperRight));
// 下側
//triangles.AddRange(SquareToTriangles(indexLowerLeft, indexLowerRight, indexFowardLowerLeft, indexFowardLowerRight));
triangles.AddRange(SquareToTriangles(indexLowerRight, indexLowerLeft, indexFowardLowerRight, indexFowardLowerLeft));
// 背面
//triangles.AddRange(SquareToTriangles(indexFowardLowerLeft, indexFowardLowerRight, indexFowardUpperLeft, indexFowardUpperRight));
triangles.AddRange(SquareToTriangles(indexFowardLowerRight, indexFowardLowerLeft, indexFowardUpperRight, indexFowardUpperLeft));
return triangles;
}
private List<int> SquareToTriangles(int lowerLeft, int lowerRight, int upperLeft, int upperRight)
{
var triangles = new List<int>();
triangles.Add(lowerRight); // 1
triangles.Add(lowerLeft); // 0
triangles.Add(upperLeft); // 2
triangles.Add(lowerRight); // 1
triangles.Add(upperLeft); // 2
triangles.Add(upperRight); // 3
return triangles;
}
キューブを作りたい場合、四角形を6面必要になるのでSquareToTriangles
のように面を構成するトライアングルを振り分けるための関数を用意しておくと楽かもしれません。
そうすることでギズモでビジュアル化した頂点を見ながら6面をそろえるだけになります。
UVを作る
単色でよければUVは不要ですがメッシュにテクスチャを張りたい場合、UVの座標が必要となります。
UVの座標を配列の0~3と4~7を同じにしています。裏から見た場合、反転した状態で張られました。
正しい向きで張るには4,5番を反転させ6,7番も反転させて格納する必要があります。
private List<Vector2> CubeUV()
{
var uvs = new List<Vector2>();
uvs.Add(new Vector2(0, 0));
uvs.Add(new Vector2(1, 0));
uvs.Add(new Vector2(0, 1));
uvs.Add(new Vector2(1, 1));
uvs.Add(new Vector2(0, 0));
uvs.Add(new Vector2(1, 0));
uvs.Add(new Vector2(0, 1));
uvs.Add(new Vector2(1, 1));
return uvs;
}
メッシュを生成して表示する
これまでに準備した頂点、トライアングル、UVでメッシュを生成します。
生成したメッシュはMeshFilter
に格納します。
public void GenerateMesh()
{
var vertices = CubePositions();
var triangles = CubeTriangles();
var uvs = CubeUV();
var mesh = new Mesh();
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
mesh.SetUVs(0, uvs);
mesh.RecalculateNormals();
var obj = new GameObject();
obj.name = "GenerateCube";
obj.transform.parent = transform;
var meshFilter = obj.AddComponent<MeshFilter>();
var meshRenderer = obj.AddComponent<MeshRenderer>();
var meshCollider = obj.AddComponent<MeshCollider>();
meshFilter.mesh = mesh;
meshCollider.sharedMesh = mesh;
meshRenderer.material = Material;
cubeMesh = meshFilter;
}
テクスチャをキューブの6面に貼る場合
上記で紹介したUVの作り方では正面と裏面の2面しかテクスチャが貼られていませんでした。
それは8頂点では足りなかったからです。
では、いくつほしいか調査するためにUnityの3Dオブジェクトのキューブを作ってみましょう。
プレイ状態でメッシュをダブルクリックすると以下のようにメッシュの情報を得られます。
Verticesが24個あるため24(頂点) / 6(面) = 4(頂点/面)
であることがわかります。
キューブのメッシュを構成するには8頂点で十分ですが、6面にテクスチャを張るためには24頂点必要になることがわかります。
任意のメッシュを生成する場合
自身で頂点を置いてみるのもいいですが、Unityの定型オブジェクトやProBuilder、Blenderで作ったメッシュを用意して頂点の情報を抜き出してみるのもいいかと思います。
時間はかかりますが、逆解析から法則を見出して数式に起こすことで理解が深まります。