2017年4月17日

Unity 動的にメッシュを作成する ~まずは四角形だ編~


スクリプトからメッシュを作成する方法について、復習がてらの解説。まずはシンプルな形から…


※このページの内容の動作確認にはUnity5.3を使用しています。
今回はUnityで動的にメッシュを作成する方法を、あんまり複雑にならないように解説してみようと思います。「動的」というのは、つまり最初から立体を用意しておくのではなくて、プログラムを走らせながらその場で作っていく、ということですね。

とりあえず最初に作るのは何の変哲もない四角形です。厚みはゼロです。

メッシュを作成する

メッシュ作成に最低限必要な情報は2つ。「それぞれの頂点がどこにあるか」という位置情報と、「どの頂点を結んで面を作るか」という情報。その2つを配列としてメッシュに渡してあげればいいのです。てなわけで

1. 頂点の位置情報

4つの頂点の位置は適当にこんな感じにしてみました。配列に格納する順番は好きなようにしてOKです。ただ、順番は面を張るときに重要になってくるので、しっかり覚えておきましょう。今回は左下、左上、右上、右下の順です。ではでは、さっそく配列を作成します。

Vector3[] vertices = {
 new Vector3(-1f, -1f, 0),
 new Vector3(-1f,  1f, 0),
 new Vector3( 1f,  1f, 0),
 new Vector3( 1f, -1f, 0)
}; 

Vector3の配列verticesを作りました。お次は面を作ります。

2. 三角形を作る頂点の順番情報

図のとおり、四角形は2つの三角形からできています。そしてこの場合、面を作成する三角形は、三角形0-1-2と三角形0-2-3ですね(さっきvertices配列に格納した順番に番号が振られていることに注意)。これをそのまま0,1,2,0,2,3と並べてint型の配列に入れます。

int[] triangles = { 0, 1, 2, 0, 2, 3 };

おしまい。

ちなみにこの並び方はとても重要で、たとえば0,1,2,2,0,3と順番を入れかえると、右下の三角形だけ面の向きが反対になってしまいます。これは、0-1-2は時計回りなのに、2-0-3が反時計回りだからです。時計回りと反時計回りをごっちゃにすると面の向きがむちゃくちゃになるので注意しましょう。

では、この2つの配列を使って実際にメッシュを作成します。

Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;

new Mesh() で空のメッシュを作成して、mesh.verticesにはさっき作成した頂点位置の配列verticesを指定。mesh.trianglesにも同様に配列trianglesを渡します。

これで一応メッシュは完成して描画もできる状態になったのですが、このままだとちょっとマズいことがあります。法線ベクトルが計算されていないのです。通常、メッシュは、それぞれの頂点に対応する法線の情報を持っています。法線とは、すんごくざっくり言うと「面の向き」のことですが、頂点にも法線があり(各頂点に隣接する面の法線ベクトルを足し合わせたものが頂点の法線ベクトルの向きになります)、実はUnityに組み込まれているシェーダーはその情報にもとづいてライティングの計算なんかを行っているわけです。しかし今のままでは法線の情報がないため、見た目が少しおかしくなってしまうのです。

なんのこっちゃ難しい話になってきたのですが、要は「法線計算しろ!」ということです。この計算、やろうと思えば手動でもできるのですが、ここはUnityにまかせましょう。

mesh.RecalculateNormals();

法線(Normals)を再計算(Recalculate)しろということで、この1行で計算してくれます。便利~!

ちなみに、法線ベクトルの情報はmesh.normalsに入っています。なので

Debug.Log(mesh.normals.Length);

で法線の数を確認してみると、再計算する前は0、再計算した後は4、というわけでちゃんと情報が入っていることがわかります。

メッシュを表示する

ただ、これだけだと単にメッシュを作っただけなので、それが実際に画面に表示されるようにしないといけません。そのためには、メッシュ情報保持担当のMeshFilterコンポーネントと描画担当のMeshRendererコンポーネントが必要になります。Unityのインスペクタ上で追加してもいいのですが、せっかくなのでこっちもスクリプトから動的に追加しちゃいましょう。

MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
if(!meshFilter) meshFilter = gameObject.AddComponent<MeshFilter>();

MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
if(!meshRenderer) meshRenderer = gameObject.AddComponent<MeshRenderer>();

meshFilter.mesh = mesh;

GetComponent<T>()でこのオブジェクトにくっついているコンポーネントを取得。もしコンポーネントがなければAddComponent<T>()を使ってその場で追加しています。meshFilter.meshにはさきほど作成したメッシュを指定します。

これで完了です。空のゲームオブジェクトにこのスクリプトをアタッチして実行!


…するとでましたお馴染みのどぎついピンク。これは「マテリアル関係がおかしくてエラーなっとるよ」ということですが、よく考えたらそもそもマテリアルを指定してませんでした。というわけで変数を追加し、インスペクタ上からマテリアルを設定できるようにします。そしてそれをmeshRendererに渡すようにしましょう。

public Material material;
meshRenderer.sharedMaterial = material;

あとは適当にマテリアルを作成してmaterial変数に指定… 今度こそ完了!ドン!


わーい。そして出来上がったのが次のスクリプトです。

using UnityEngine;
using System.Collections;

public class SquareMesh : MonoBehaviour {

 public Material material;

 void Start()
 {
  Vector3[] vertices = {
   new Vector3(-1f, -1f, 0),
   new Vector3(-1f,  1f, 0),
   new Vector3( 1f,  1f, 0),
   new Vector3( 1f, -1f, 0)
  };

  int[] triangles = { 0, 1, 2, 0, 2, 3 };

  Mesh mesh = new Mesh();
  mesh.vertices = vertices;
  mesh.triangles = triangles;
  
  mesh.RecalculateNormals();

  MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
  if(!meshFilter) meshFilter = gameObject.AddComponent<MeshFilter>();

  MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
  if(!meshRenderer) meshRenderer = gameObject.AddComponent<MeshRenderer>();

  meshFilter.mesh = mesh;
  meshRenderer.sharedMaterial = material;
 }
}

空のゲームオブジェクトにアタッチしてマテリアルを指定、あとは実行するだけで片面の四角形が描画されるはずです。そして、verticesとtrianglesを地道に指定していけば、3Dモデリングソフトなんか使わなくても好きな形が作れるはず!可能性が広がりまくりじゃー!

<次回> 地形を作るぞ編

0 件のコメント:

コメントを投稿