こんにちわ、Unityエンジニアのオオバです。

「Unityのスクリプトからメッシュを作りたくないですか?」

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

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

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

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

この記事の内容

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

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

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

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

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

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

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

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

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

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

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

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

描画する三角形のの仕様

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

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

三角形を描画する方法はいくつかあります。
1つの三角形に対し1つのGameObjectを使う
という方法を取ってみます。

では、さっそく始めていきましょう。

準備としてMonoBehaviourクラスを継承した
👇DynamicCreateMeshクラスを作成、

using UnityEngine;  
using System.Collections;  

[RequireComponent (typeof(MeshRenderer))]  
[RequireComponent (typeof(MeshFilter))]  
public class DynamicCreateMesh : MonoBehaviour  
{
    private void Start ()  
    {
        var mesh = new Mesh ();  
        mesh.vertices = new Vector3[] {  
            new Vector3 (0, 1f),  
            new Vector3 (1f, -1f),  
            new Vector3 (-1f, -1f),  
        };  
        mesh.triangles = new int[] {  
            0, 1, 2  
        };  
        mesh.RecalculateNormals ();  
        var filter = GetComponent<MeshFilter> ();  
        filter.sharedMesh = mesh;  
    }
}

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インスタンスに頂点座標配列をセット  
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 ();  
filter.sharedMesh = mesh;  

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

おまけ処理

前述の通りMeshFilterMeshRendererは、
メッシュを表示するためには必ず必要です。

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

↑のコードのRequeireComponentアトリビュートを使い、
DynamicCreateMeshをAddComponentした時に、
自動でAddComponentされるようにしています。

トラブル回避のTipsですね。

三角形メッシュ描画できた!

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

この段階ではピンク色の三角形になってしまいますが、
無事に表示されました。

2.メッシュを着色する

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

しかし、メッシュの色はピンク。
このピンクはUnity内部エラーが起きた証拠😰
これからメッシュに対して正しく着色をしていきましょう。

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

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

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

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

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

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

  1. Materialの作成
  2. Materialにシェーダーを設定して描画する色を指定
  3. MeshRendererにMaterialをセットする
  4. 三角形に着色

1. Materialの作成

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

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

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

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

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

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

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

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

3. MeshRendererにMaterialをセットする

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

using UnityEngine;  
using System.Collections;  

[RequireComponent (typeof(MeshRenderer))]  
[RequireComponent (typeof(MeshFilter))]  
public class DynamicCreateMesh : MonoBehaviour  
{
  // 変更箇所 : Materialを保持するようにする  
  [SerializeField] private Material _mat;  

  private void Start ()  
  {
    var mesh = new Mesh ();  
    mesh.vertices = new Vector3[] {  
      new Vector3 (0, 1f),  
      new Vector3 (1f, -1f),  
      new Vector3 (-1f, -1f),  
    };  
    mesh.triangles = new int[] { 0, 1, 2 };  

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

    // 変更箇所 : MeshRendererからMaterialにアクセスし、Materialをセットするようにする  
    var renderer = GetComponent<MeshRenderer> ();  
    renderer.material = _mat;  
  }
}

DynamicCreateMesh1.cs · GitHub

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

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

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

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

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

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

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

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

1. 画像の用意

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

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

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

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

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

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

試しに実行してみます。

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

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

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

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

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

UV座標を理解しよう

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

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

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

DynamicCreateMesh2.cs · GitHub

Vector2型の配列でMeshに渡す

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

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

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

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

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

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

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

では、次のような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座標に変換しているのです。

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

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

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

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

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

全体で40行弱のソースコードでしたが、
いかがだったでしょうか。
3Dプログラミングの前提知識がないと、
少し難しかったかもしれません。

超基本編と言いながら、
だいぶボリューミーな内容でしたしね。

3D描画の勉強をUnityから始めるのは、
個人的にはありだと主張します。

OpenGL、DirectXから始めるのはオススメしません

理由は難しすぎるからです。

オオバも実際にOpenGL、DirectXともに
書いたことありますが、
難しくて楽しくないんですよね。。。

まずは今回紹介したような、
少しがんばれば達成できそうな難易度から入ると、
その後の学習も継続できるのではと思います。

難しすぎると、諦めてしまう。
そんな学習教材が作れると良いなと思います。

引き続き3Dのチュートリアルを作っていければと思います。
オオバ自身も含めて勉強です。

ではまたお会いしましょう。

オススメ記事
2021秋 Asset Refreshセール
100以上のアセットがなんと50%OFF!!オオバもいくつか買いました!
期間 : 10月2日午後3時59分まで
検証環境