Skip to main content

3Dモデルの仕組み(その1)

 今まで、FBXファイルでモデルをダウンロードし、そのまま使ってきましたが、実際にどう作られているのか調べてみましょう。

頂点(Vertex / バーテックス)

 頂点は3D空間の「ある位置」を表している。普通、3次元ベクトルとして表現する。この位置を適切に繋がることにより、3Dの形状を作成できる。例えば、普通の立方体は頂点は8点がある:

image.png

頂点の配列

 

0 (-1, -1, -1)
1 ( 1, -1, -1)
2 ( 1, 1, -1)
3 (-1, 1, -1)
4 (-1, -1, 1)
5 ( 1, -1, 1)
6 ( 1, 1, 1)
7 (-1, 1, 1)

 

 Unityの場合は「Vertex3」型の配列で頂点を作成ができる。それぞれの頂点に「指数」(Index)を割り当てる必要がある。上の図だと、「0〜7」の指数をアサインした。ただし、必要に応じて、同じ頂点を重複する必要な場合もある。このときは、同じ位置に複数の頂点があり、それぞれが別々の指数を持っている。

image.pngimage.png

頂点の配列

 

0
8
16 (-1, -1, -1) 1 9 17 ( 1, -1, -1) 2 10 18 ( 1, 1, -1) 3 11 19 (-1, 1, -1) 4 12 20 (-1, -1, 1) 5 13 21 ( 1, -1, 1) 6 14 22 ( 1, 1, 1) 7 15 23 (-1, 1, 1)

 ここでは、各面は自分の頂点を持ち、隣の面と共有しない。つまり、この場合は6面 × 4点=24点である。

三角形(Triangle)

 頂点3点を繋がると三角形(Triangle)を作ることができ、三角形の群は「メッシュ」(Mesh)という。

image.png

 三角形を作成するときに、次の点を考慮しないといけない:

① 頂点の順番(三角形は時計回りか?逆時計回りか?)

 3つの頂点の繋がり方は2つある:時計回り(Clockwise - CW)と反時計回り(Counter - Clockwise - CCW)。ゲーム開発では、この順番はとても重要である。これは「Winding Order」(巻き順)という。

 ゲームエンジンの最適化と高速化のため、無駄な面(三角形)を計算処理から抜いた方が良いでしょう。普通、カメラに映らない面(カメラの後ろにあるもの)を計算から抜く。また、カメラに向いていない面(三角形の裏側)も、計算から抜くことが一般である。

 三角形の「表」と「裏」が分かるには、頂点の順番を使う。Unityの場合は「時計回りの三角形は表面である」となっているが、別のエンジンは逆になる可能性があるので、注意してください。

image.png

② 頂点を共有するか?別々にするのか?

 2枚の三角形は1つの辺が接着しているときに、2つの選択しがある:

    同じ頂点を使いまわし、共有する 頂点を重複し、面を作成するとき、別々の頂点を使う

     とくにどれにするかは別にルールがなく、ゲームでやりたいことにより、適切な繋がり方を選ぶべきが、一般的には:

      なめらかなカーブを表現しようとし、同じ面であるとき→頂点を共有する(連続する面)
        例:キャラクター、服装、植物、動物 硬い角を持っているオブジェ、別の面であるとき→別々の頂点にする(連続しない面)
          例:壁、さいころ、ブロック、かくかくしかじかの形

          ならば、普通の立方体の繋がりは、以下のようになる:

          image.png
          各面が自分の頂点を持っているが、同じ面の三角形は頂点を共有する

          メッシュ(Mesh)

           メッシュは三角形の群である。メッシュはあくまで「形状」を定義し、色や模様は別途で作成しなければならない(シェーダーのときにまた学ぼう!)

           Unityでは「Mesh」というクラスがある。この「Mesh」クラスは、頂点と三角形を定義するクラスである。また、光の計算のための「法線ベクトル」や模様を貼り付けるための「UV座標」も持っている(これもシェーダーの時に学ぼう)

           メッシュができたら、「MeshFilter」コンポーネントに渡し、「MeshRenderer」で描画する。

          image.png

          練習

          「MyCube」クラスを作成し

            メッシュを作成し
              立方体の頂点を作成 頂点をつなげて、三角形を作成 マテリアルをInspectorで設定 実行中に MeshFilter と MeshRenderer を追加し、描画する。
              MyCube スクリプト
              // 立方体のメッシュを作成し、描画する
              public class MyCube : MonoBehaviour
              {
              	// 立方体のマテリアル
              	[SerializeField] 
              	private Material material;
              
              	// 作成したメッシュ
              	private Mesh mesh;
              	
              	private void Start()
              	{
              		CreateMesh();
              		
              		// ゲームオブジェクトにMeshFilterを追加
              		MeshFilter mf = gameObject.AddComponent<MeshFilter>();
              		mf.mesh = mesh;
              		
              		// MeshRendererも追加
              		MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
              		mr.material = material;
              	}
              
              	// メッシュを作成し、MeshFilterに割り当てる
              	private void CreateMesh()
              	{
              		// メッシュを作成
              		mesh = new Mesh();
              
              		//頂点を作成し、設定する
              		mesh.vertices = CreateVertex();
              		
              		// 三角形を作成し、設定する
              		mesh.triangles = CreateTriangle();
              
              		// 自動的に法線ベクトルを計算
              		mesh.RecalculateNormals();
              	}
              	
              	// 頂点を作成する
              	// 返す:頂点の配列
              	private Vector3[] CreateVertex()
              	{
              		Vector3[] vertex = new Vector3[24];
              
              		vertex[0] = new Vector3(-1, -1, -1);
              		vertex[1] = new Vector3(1, -1, -1);
              		vertex[2] = new Vector3(1, 1, -1);
              		vertex[3] = new Vector3(-1, 1, -1);
              		vertex[4] = new Vector3(-1, -1, 1);
              		vertex[5] = new Vector3(1, -1, 1);
              		vertex[6] = new Vector3(1, 1, 1);
              		vertex[7] = new Vector3(-1, 1, 1);
              
              		vertex[8] = new Vector3(-1, -1, -1);
              		vertex[9] = new Vector3(1, -1, -1);
              		vertex[10] = new Vector3(1, 1, -1);
              		vertex[11] = new Vector3(-1, 1, -1);
              		vertex[12] = new Vector3(-1, -1, 1);
              		vertex[13] = new Vector3(1, -1, 1);
              		vertex[14] = new Vector3(1, 1, 1);
              		vertex[15] = new Vector3(-1, 1, 1);
              
              		vertex[16] = new Vector3(-1, -1, -1);
              		vertex[17] = new Vector3(1, -1, -1);
              		vertex[18] = new Vector3(1, 1, -1);
              		vertex[19] = new Vector3(-1, 1, -1);
              		vertex[20] = new Vector3(-1, -1, 1);
              		vertex[21] = new Vector3(1, -1, 1);
              		vertex[22] = new Vector3(1, 1, 1);
              		vertex[23] = new Vector3(-1, 1, 1);
              
              		return vertex;
              	}
              
              	// 三角形を作成する
              	// 返す:三角形の配列(頂点の指数を3個ずつ)
              	private int[] CreateTriangle()
              	{
              		int[] tri = new int[36];
              
              		int i = 0;
              
              		// 前
              		tri[i++] = 0;
              		tri[i++] = 3;
              		tri[i++] = 1;
              
              		tri[i++] = 1;
              		tri[i++] = 3;
              		tri[i++] = 2;
              
              		// 右
              		tri[i++] = 9;
              		tri[i++] = 10;
              		tri[i++] = 6;
              
              		tri[i++] = 9;
              		tri[i++] = 6;
              		tri[i++] = 5;
              
              		// 下
              		tri[i++] = 20;
              		tri[i++] = 16;
              		tri[i++] = 17;
              
              		tri[i++] = 20;
              		tri[i++] = 17;
              		tri[i++] = 21;
              
              		// 左
              		tri[i++] = 7;
              		tri[i++] = 8;
              		tri[i++] = 4;
              
              		tri[i++] = 7;
              		tri[i++] = 19;
              		tri[i++] = 8;
              
              		// 後ろ
              		tri[i++] = 14;
              		tri[i++] = 12;
              		tri[i++] = 13;
              
              		tri[i++] = 14;
              		tri[i++] = 15;
              		tri[i++] = 12;
              
              		// 上
              		tri[i++] = 18;
              		tri[i++] = 11;
              		tri[i++] = 23;
              
              		tri[i++] = 18;
              		tri[i++] = 23;
              		tri[i++] = 22;
              
              		return tri;
              	}
              }