渋谷ほととぎす通信

新しいこと・枯れたこと問わずオオバが興味を持ったものを調査し実践する効率を求める完全趣味の技術ブログ

Unity 動的にメッシュを生成してゴニョゴニョする : 超基本編


f:id:esakun:20210206045616j:plain

「プログラムでメッシュを作りたくないですか?」

プログラムからメッシュを作ることができれば、
自由自在にメッシュを操作し、
ゆくゆくは自分の描く3Dの世界を構築することが出来ます。

とはいえ千里の道も一歩から

今回は超基本編として3Dプログラミングの
初心者
に送る記事を書いてみました。

実際に手を動かしながらやっていきましょう!!

1. 全ての始まりは三角形メッシュ

メッシュの最小単位は三角形。

三角形のメッシュを表示させることが、
「3Dプログラミングの基礎」
いわゆるHelloWorldのようなものです。

全てはここから始まります。

自力で三角形を描画することが重要

三角形メッシュの描画処理を自力で実装してみると、
Unityが当たり前のようにやってくれている3Dモデルの
表示の仕組みが分かってきます。

これを発展させていくことでゲームづくりの表現方法が広がります。
さらにパフォーマンスチューニングの知識を得ることも可能です。

まずはUnityで1個の三角形メッシュを表示させていきます。

メッシュとは「頂点・辺・面」の集合体

メッシュというのは一般的にポリゴンメッシュの事を指します。
ポリゴンメッシュとは3Dオブジェクトを表す、
「頂点・辺・面」の集合体の事です。

一般的に表示できるメッシュは3点もしくは4点を結んだ
三角形メッシュ四角形メッシュの2種類です。

Unityは三角形メッシュと四角形メッシュ両方に対応しています。

描画する三角形のシンプルな仕様

f:id:esakun:20151129152423p:plain:w200

こんな感じの1辺が2メートルの三角形メッシュを表示してみます。

※Unityの単位はメートルです

三角形を描画する方法はいくつかあります。
今回は最もシンプルな1つの三角形に対し1つのGameObject
使う方法を取ります。

動的に三角形を描画する準備としてMonoBehaviourクラスを継承した
👇DynamicCreateMeshクラスを作成しました。

Startメソッドに三角形描画に関する処理を全て書いているため、
Unity再生時このコンポーネントをGameObject
AddComponentすると三角形が描画されます。

三角形描画の処理の流れ

  1. Meshインスタンスを作成
  2. Meshに頂点座標配列を渡す
  3. Meshに頂点の順番配列を渡す
  4. MeshRendererの用意
  5. MeshFilterにMeshを渡す

1つ1つ処理の内容を見ていきましょう。

1. Meshインスタンスを作成

動的にメッシュを作成するために、
Meshインスタンスをnewして作ります。

var mesh = new Mesh ();

作成したMeshには以下の情報が必要です。

  • 頂点座標配列
  • 頂点の順番配列

2. Meshに頂点座標配列を渡す

三角形を作る上で必要な頂点数は3つ
それを配列で渡します。

今回必要な頂点の情報は座標ですので、 Vector3型の配列を作ります。

Meshクラスのverticesプロパティに頂点座標配列をセットします。

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

Meshに渡す頂点座標配列は
GameObjectを原点にしたローカル座標
であることに注意してください。

3. Meshに頂点の順番配列を渡す

メッシュを正しく描画させるためには、
頂点の描画順を指定する必要があります。
それがMeshクラスのtrianglesプロパティです。

頂点の順番をIndex(インデックス)と呼びます。
trianglesに先のインデックス配列を渡します。

インデックス配列は頂点の順番配列なのでint型の配列です。

mesh.triangles = new int[] {
    0, 1, 2  
};

頂点を結ぶ順番配列(trianglesプロパティ)
メッシュを正しく表示させるためにとても重要な情報です。

Unityは時計回りに頂点を結んだ面が前面になります。
シェーダのカリング設定がデフォルトであれば、
前面にメッシュが表示されます。

前面というのは法線(面の向き)を指定するということです。

もしメッシュが表示されない、
表示がおかしいなどあれば、
この頂点を結ぶインデックス配列
またはシェーダを見直すと良いでしょう。

// 法線ベクトルの再計算処理
mesh.RecalculateNormals ();

またRecalculateNormalsメソッドで
法線方向の再計算を行います。

これを呼ばないと法線方向が(0, 0, 1)固定になってしまい、
今後ライティングなどの際に意図しない表示になるかもしれません。

特に理由がない場合は必ず呼ぶと良いでしょう。

4. MeshRendererの用意

Unityでメッシュを表示させるためにはMeshRenderer
またはSkinnedMeshRendererが必要です。

今回のメッシュはスキニングされていないため
(アニメーションしないメッシュの事)
MeshRendererを使用することになります。

※スキニングメッシュとはボーンを使って 頂点を動的に動かすことが出来るメッシュのことです。 例えばUnity-chanはスキニングされたメッシュで動いています

MeshRendererを事前にGameObjectへAddComponentしておきましょう

5. MeshFilterにMeshを渡す

MeshRendererに加えMeshFilterも必要です。

Mesh Filter はアセットからメッシュを取得し、画面上でのレンダリングするために、メッシュレンダラー に渡します。

メッシュフィルター - Unity マニュアルより

マニュアルの通りMeshFilterMesh
MeshRendererに渡すために必要です。
Meshを直接レンダラーに渡さない仕様にしているのは、
MeshFilterを通すことでMeshRenderer
SkinnedMeshRendererをフィルタリングして、
事故を減らしてくれているのではないかと勝手に思っています。

var filter = GetComponent<MeshFilter> ();
filter.sharedMesh = mesh;

このようにMeshFilterに先ほど作成したMeshインスタンスを渡します。

おまけ

[RequireComponent (typeof(MeshRenderer))]
[RequireComponent (typeof(MeshFilter))]

念のための処理ですが、MeshFilterMeshRendererは、
GameObjectでメッシュを表示するためには必ず必要なので、
RequeireComponentアトリビュートを使って*DynamicCreateMeshを
AddComponentした時に自動でAddComponentされるようにしています。

これも事故を少なくするTipsです。

f:id:esakun:20151129153339p:plain:w200
この段階ではピンク色の三角形になってしまいますが、
無事に表示されました。

【休憩】オススメ3Dプログラミング書籍

さて、3Dプログラミングが面白くなってきたのではないでしょうか。
僕も趣味や仕事で3Dプログラミングをしています。

更に一歩先のレベルに進むための書籍を紹介します。
西川善司さんの 「ゲーム制作者になるための3Dグラフィックス技術」です。

本記事で紹介している三角形1つを描画する話からかなり先に進んでいますが、
3Dプログラミングがどのようにゲームで活用されているかが、
とてもわかりやすく解説されていますので、とてもオススメです。

あわせて読みたい
他にも動的にメッシュを生成する記事を執筆しているのでどうぞ。
Unity動的にメッシュを生成するシリーズ - 渋谷ほととぎす通信

2.メッシュを着色する

三角形メッシュは無事に表示されました。
おつかれさまでした。

しかし、メッシュの色はピンク。
ちなみにこのピンク色はUnity内部でエラーであることの証拠😰
これからメッシュに対して正しく着色をしていきます。

ここでシェーダが登場します。
シェーダとは「3Dオブジェクトをディスプレイに映し出すためのプログラム」です。

UnityではシェーダをMaterialというオブジェクトに内包します。
MaterialMeshRendererに渡してメッシュ描画を行います。

ちなみにUnityはMaterialの設定が無い場合、
親切にピンク色で描画してくれます。
必ずMaterialは必要だということです。

余談になりますがWebGLやDirectXでは、
シェーダを設定していないメッシュは何も表示されません
何が原因なのかを調べるだけで時間を取られます。
Unityでは先のようにピンク色でアラート表示してくれるため、
原因がすぐわかります。

このことからUnityはホスピタリティの高い神だと言えます。

👇三角形メッシュの着色工程は次の通りです。

  1. Materialの作成
  2. Materialにシェーダを設定して描画する色を指定
  3. MeshRendererにMaterialをセットする
  4. 三角形に着色
シェーダの理解を深めるためのオススメ記事

1. Materialの作成

Materialを使って三角形メッシュに シェーダの適用をしていきます。

f:id:esakun:20190102025845p:plain
Project > Createボタン > Mateiralsから
SampleMaterialという名前で、 新規のMaterialオブジェクトを作成します。

2. Materialにシェーダを設定して描画する色を指定

f:id:esakun:20151129162853p:plain

SampleMaterialを選択状態にして、
Shaderの設定をプルダウンからUnlit/Colorに設定します。

Main Colorの項目にに適当な色を設定します。

ちなみにUnlit/Colorとは、
以下の機能を備えたシンプルで軽量なシェーダです。

  • ライト反映しない
  • 陰影処理をしない
  • Main Colorでピクセルを塗る

3. MeshRendererにMaterialをセットする

DynamicCreateMeshクラスのソースを一部変更し、
Materialを反映できるようにします。

SampleMaterialをDynamicCreateMeshの
_mat変数にセットしておきます。

f:id:esakun:20151129162716p:plain

4. 三角形に着色できた!

f:id:esakun:20151129163335p:plain:w200
この状態でUnityを実行すると、
次のように黄色く着色された三角形メッシュ
表示されました。

あわせて読みたい

頂点カラーを使った描画もあわせてどうぞ
Unity 動的に生成したメッシュに対して頂点カラーを反映する - 渋谷ほととぎす通信

簡単なシェーダで面白そうな表現を作ってみました
横にずれるノイズ風イメージエフェクト - 渋谷ほととぎす通信

https://cdn-ak.f.st-hatena.com/images/fotolife/e/esakun/20170418/20170418164011.gif

3. 三角形メッシュに画像を貼ってみる

三角形メッシュの着色の次は、
画像(テクスチャ)をメッシュに貼っていきます。

(以下画像の事をテクスチャと呼称しています)

  1. 画像の用意
  2. シェーダをUnlit/Textureを使用
  3. テクスチャの座標を決めるUV座標

1. 画像の用意

f:id:esakun:20151129164400p:plain:w200

このテクスチャをメッシュに
貼り付けてみようと思います。

2. シェーダをUnlit/Textureを使用

f:id:esakun:20151129164546p:plain:w320

先ほど作ったSampleMaterialを選択し、
シェーダをUnlit/Textureに変更して
上のインスペクターの通りテクスチャを設定します。

、先程登場したUnlit/Colorと同様で、
ライトの影響を受けない軽量なシェーダです。
指定した色ではなく、指定したテクスチャで描画します。

  • ライト反映しない
  • 陰影処理をしない
  • テクスチャのカラーでピクセルを塗る

試しに実行してみます。

f:id:esakun:20151129164930p:plain:w200

すると予想に反して青い三角形が表示されます。

3. テクスチャの座標を決めるUV座標

なぜ青い三角形が表示されたのか、
それは各頂点に対してUV座標が設定されていなかったからです。

UV座標とはテクスチャの各ピクセルが
三角形のどの場所に描画するかの材料
になります。

UV座標を理解しよう

f:id:esakun:20151129172803p:plain:w450

UV座標はテクスチャの座標です。

  • 左下(原点) :(0.0, 0.0)
  • 右上 :(1.0, 1.0)
  • 横軸 : U座標
  • 縦軸 : V座標
  • 座標は0.0 〜 1.0の範囲をとる

このイメージを頭に入れて、
各頂点に対してUV座標をセットする
コードに修正していきます。

Vector2型の配列でMeshに渡す

修正箇所はmeshのuvプロパティ
Vector2型の配列を渡しているところです。

mesh.uv = new Vector2[] {
    new Vector2 (0.5f, 1f),
    new Vector2 (1f, 0),
    new Vector2 (0, 0),
};

UV座標が反映されて意図通りに描画

f:id:esakun:20210225114940p:plain:w450

この配列は画像がちょうど三角形に収まるUV座標になっていますね。

画像の一部を表示させてみる

f:id:esakun:20151129174004p:plain:w450

では、次のようなUV座標を代入してみるとどうなるでしょうか。

float texSize = 256f;
mesh.uv = new Vector2[] {
    new Vector2 (86f / texSize, 100f / texSize),
    new Vector2 (116f / texSize, 42f / texSize),
    new Vector2 (60f / texSize, 42f / texSize),
};

何をしているかというと指定した座標を(0 〜 1)、
すなわちUV座標に変換しているのです。

f:id:esakun:20151129174136p:plain

するとUV座標で指定した部分がメッシュに描画されます。
おもしろいですよね。

Unityで動的にメッシュを生成してゴニョゴニョしてみたまとめ

三角形を1つ描画する中で以下のことを学んできました。

  1. 三角形メッシュを生成
  2. メッシュをカラーで描画
  3. UV座標を設定してメッシュにテクスチャの描画

超基本編と言いながら、だいぶボリューミーな内容でした。
分からない所は少しずつ学習して頂ければと思います。

やはりUnityは神

今回は40行弱のソースコードで三角形を描画できてしまいました。
3Dプログラミングの前提知識がないと、
若干難しかったかもしれません。

ただWebGLやDirectXをフルスクラッチで書く場合は
こんなものではありません。

  • 頂点情報のバインド
  • テクスチャのロード処理
  • シェーダとの関連付け

などなど。
とても手間のかかる手続きが必要になります。

Unityではそういった煩わしいことは一切ありません。
こんなに簡単に三角形を描画できてしまうのは、
繰り返しになりますがと言わざるを得ません。

最後に

TwitterでメンションまたはDMを送って頂ければ
分かる範囲でお答えいたします。

👉オオバのTwitterアカウント

またTwitterでは毎日便利なUnity機能の紹介をしているので、
お気軽にフォローお願いいたします。

一緒にUnityを学んでいきましょう。

こちらの記事もあわせてどうぞ