UnityEngine.Meshの内部構造解説

# 自己紹介
![cat2.png](https://qiita-image-store.s3.amazonaws.com/0/52576/904d522d-09f7-8c67-6796-25af57f10fc7.png)

のたぐす(@notargs)
- シェーダー書いたりUI作ったりしてます

## LGTM Shaders
![LGTM](https://github.com/notargs/LGTM-Shaders/raw/master/Shader010.gif)
![LGTM](https://github.com/notargs/LGTM-Shaders/raw/master/Shader009.gif) 


# よく聞く話
- 「Meshってデザイナーが弄るものでしょ？」
- 「Meshってゲームエンジンに任せるものでしょ？？」
- 「Meshを動的に切断したいけど全然イメージつかない」

# 今回の話の概要
- Mesh怖くないよ！！
- UnityのMeshを使いこなせば**プログラマーでもモデリングできる！**という話

![SnapCrab_NoName_2016-12-6_11-18-34_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/df794e07-1df9-e3bb-f47a-5f36507231fc.png)

# 内容
- Meshの内部構造について解説
- スクリプトからMeshを組み立てる方法
- 応用例

# どういうときに役立つの？
- 幾何形体など、スクリプトで生成した方が早いとき
- 同じ物体を等間隔に並べたいとき
- SetPassCall削減
- モデリングツールを持っていないとき

# 基礎編: Meshが持つ情報

## Vertex(頂点位置)
- 各頂点の位置
- 内部はただのVector3型の配列

![temp.png](https://qiita-image-store.s3.amazonaws.com/0/52576/44b0325d-b613-acc1-e652-01342f2345aa.png)

## Normal(法線)
- 各頂点についての面の向き
- ライティングや衝突判定などに活用される
- Vertexと同じ長さのVector3型配列

![temp.png](https://qiita-image-store.s3.amazonaws.com/0/52576/c40061cc-da62-55cc-99c5-d70c86ac102c.png)

## Tangent(接線) ＆ Binormal(従法線)
- 各頂点についてのNormalに対して直交なベクトル
- ライティング時にNormalを補助する形で使われる
- 法線マップテクスチャを張るときなどに必須

![temp.png](https://qiita-image-store.s3.amazonaws.com/0/52576/b7ed6e69-cba6-4ca3-54b1-8dc5d8094c39.png)

---

## UV1 ~ UV4(テクスチャ座標)
- 頂点ごとのテクスチャ位置
- 1つの頂点につき4個まで持つことができる
- 2個目以降は複数枚のテクスチャを重ねたい時などに活用

![SnapCrab_NoName_2016-12-6_10-49-16_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/54991ea3-2447-ee5f-f615-28b52017af7c.png)

## Color(頂点カラー)
- 頂点ごとの色情報
- Standardシェーダーでは出力されない点は注意
- 下の画像はSpriteシェーダー

![SnapCrab_NoName_2016-12-6_10-56-51_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/12534adb-3097-242c-dc63-0c4d11ffae4c.png)

## Index(頂点インデックス)
- ポリゴンを定義する
- 配列上での頂点のインデックスを3つずつ並べて指定
- 時計回りor反時計回りでポリゴンの裏or表が決まる

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/52576/3430b18f-d000-d87a-c685-46432410126d.png)

## Bounds(領域)
- Mesh全体を覆う長方形
- カメラから見た内外判定などに使われている

![SnapCrab_NoName_2016-12-6_11-18-34_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/df794e07-1df9-e3bb-f47a-5f36507231fc.png)

# 応用編1: Quadを自作してみる

- 4つの頂点を定義
- 2つのポリゴンを定義

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/52576/3430b18f-d000-d87a-c685-46432410126d.png)

```MeshCreator.cs
void Start()
{
    // Meshを作成
    var mesh = new Mesh();

    // 頂点を設定
    mesh.vertices = (
        from y in new[] {-1, 1}
        from x in new[] {-1, 1}
        select new Vector3(x, y, 0)).ToArray();

    // 頂点インデックスを設定
    mesh.triangles = new[] {0, 1, 2, 2, 1, 3};

    // 領域と法線を自動で再計算する
    mesh.RecalculateBounds();
    mesh.RecalculateNormals();

    // MeshFilterに設定
    GetComponent<MeshFilter>().mesh = mesh;
}
```

## MeshFilterとMeshRendererを持ったGameObjectを作って先ほどのスクリプトをアタッチ

![SnapCrab_NoName_2016-12-6_11-23-11_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/0b026c7e-fe8d-3edb-3107-3ff77ca15636.png)

## 実行するとQuadが表示される

![SnapCrab_NoName_2016-12-6_11-24-12_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/b9aad530-15c0-f878-a08d-97bd1693ddc3.png)

## 大量に配置することでパーティクルにも
![無題7.png](https://qiita-image-store.s3.amazonaws.com/0/52576/ed778087-08ed-830f-dbf8-e88b62d641e2.png)

# 応用編2: ペンパイナッポーアッポーペンを自作してみる

## ペンの設計

![pen.jpeg](https://qiita-image-store.s3.amazonaws.com/0/52576/66e74d16-55bb-2493-b866-9447cb68474c.jpeg)

## ペンの実装
```c#
void Pen(Matrix4x4 world)
{
    var indexOffset = vertices.Count;

    const int ySize = 11;
    const int xSize = 6;
    vertices.AddRange(
        from y in Enumerable.Range(0, ySize)
        from x in Enumerable.Range(0, xSize)
        from y2 in new[] { -0.5f, 0.5f }.Select(v => v + y - ySize / 2.0f)
        from x2 in new[] { -0.5f, 0.5f }.Select(v => v + x - xSize / 2.0f)
        select world.MultiplyPoint(Quaternion.AngleAxis(x2 / xSize * 360.0f, Vector3.up) * Vector3.forward
        * Mathf.Clamp01(y2 + ySize / 2) * 0.05f + Vector3.up * y2 / ySize * 2.0f)
    );

    colors.AddRange(Enumerable.Range(0, vertices.Count - indexOffset).Select(_ => new Color(0.2f, 0.1f, 0.1f)));

    foreach (var x in Enumerable.Range(0, (vertices.Count - indexOffset) / 4))
    {
        triangles.AddRange(new []
        {
            0, 1, 2,
            2, 1, 3,
        }.Select(i => indexOffset + x * 4 + i));
    }
}
```
![SnapCrab_NoName_2016-12-9_19-43-49_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/039c166f-44d3-8697-9f55-50782ab7e5cc.png)

## パイナッポーの設計

![pineapple.jpeg](https://qiita-image-store.s3.amazonaws.com/0/52576/9d690f5f-ae46-64ff-34f8-4ae508d796ba.jpeg)

## パイナッポーの葉っぱの実装
```c#
void PineappleGlass(Matrix4x4 world)
{
    var indexOffset = vertices.Count;

    const int ySize = 7;
    const int xSize = 4;
    vertices.AddRange(
        from y in Enumerable.Range(0, ySize)
        from x in Enumerable.Range(0, xSize)
        from y2 in new[] { 0, 1 }.Select(v => v + y)
        from x2 in new[] { 0, 1 }.Select(v => v + x - xSize / 2.0f)
        from vec in new [] { Quaternion.AngleAxis(x2 / xSize * 360.0f + 45, Vector3.up) * new Vector3(0, y2, 1) }
        select world.MultiplyPoint(new Vector3(vec.x * (1 - vec.y / ySize), vec.y * 0.5f, vec.z * 0.3f + Mathf.Pow(vec.y / ySize * 2.0f, 2.0f)))
    );

    colors.AddRange(Enumerable.Range(0, vertices.Count - indexOffset).Select(_ => new Color(0.2f, 0.6f, 0.2f)));

    foreach (var x in Enumerable.Range(0, (vertices.Count - indexOffset) / 4))
    {
        triangles.AddRange(new[]
        {
            0, 1, 2,
            2, 1, 3,
        }.Select(i => indexOffset + x * 4 + i));
    }
}
```
## パイナッポーの実装

```c#
void Pineapple(Matrix4x4 world)
{
    var indexOffset = vertices.Count;

    const int ySize = 11;
    const int xSize = 20;
    vertices.AddRange(
        from y in Enumerable.Range(0, ySize)
        from x in Enumerable.Range(0, xSize)
        from y2 in new[] { 0, 1 }.Select(v => v + y - ySize / 2.0f)
        from x2 in new[] { 0, 1 }.Select(v => v + x - xSize / 2.0f)
        select world.MultiplyPoint(Quaternion.AngleAxis(x2 / xSize * 360.0f, Vector3.up)
        * new Vector3(0, Mathf.Sin(y2 / ySize * Mathf.PI) * 1.3f, Mathf.Cos(y2 / ySize * Mathf.PI)) * Mathf.Cos(y2 * 0.1f))
    );

    colors.AddRange(Enumerable.Range(0, vertices.Count - indexOffset).Select(_ => new Color(0.6f, 0.6f, 0.2f)));

    foreach (var x in Enumerable.Range(0, (vertices.Count - indexOffset) / 4))
    {
        triangles.AddRange(new []
        {
            0, 1, 2,
            2, 1, 3,
        }.Select(i => indexOffset + x * 4 + i));
    }

    for (var i = 0; i < 30; ++i)
    {
        PineappleGlass(world * Matrix4x4.TRS(Vector3.up * (1 + Random.Range(0.0f, 1.0f)), Quaternion.AngleAxis(Random.Range(0, 360), Vector3.up), Vector3.one * 0.3f));
    }
}
```
## パイナッポー
![SnapCrab_NoName_2016-12-10_14-3-18_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/e9b37348-1e2b-573d-7572-4eade2a6cb4b.png)

## アップルの設計

![apple.jpeg](https://qiita-image-store.s3.amazonaws.com/0/52576/85504f11-8218-4b78-0349-04d480ca3ecf.jpeg)

## アップルの試作
造形が難しいのでGLSL Sandboxで試作
![SnapCrab_NoName_2016-12-9_8-53-37_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/9613ba27-e22f-5e74-3ff1-cbc0a7a67eb7.png)

## アップルの実装

```c#

static Vector3 ProtApple(float t)
{
    t += 0.5f;
    var p = new Vector2(Mathf.Sin(t * Mathf.PI), Mathf.Cos(t * Mathf.PI)) * 0.3f * (Mathf.Pow(t, 0.8f) * Mathf.Pow(1.0f - t, 0.1f) + 0.3f);
    if (t < 0.02f)
    {
        p.y = 0.2f;
    }
    return p;
}

void Apple(Matrix4x4 world)
{
    var indexOffset = vertices.Count;
        
    const int ySize = 80;
    const int xSize = 30;
    vertices.AddRange(
        from y in Enumerable.Range(0, ySize)
        from x in Enumerable.Range(0, xSize)
        from y2 in new[] { 0, 1 }.Select(v => v + y - ySize / 2.0f)
        from x2 in new[] { 0, 1 }.Select(v => v + x - xSize / 2.0f)
        select world.MultiplyPoint(Quaternion.AngleAxis(x2 / xSize * 360.0f, Vector3.up) * ProtApple(y2 / ySize))
    );

    colors.AddRange(Enumerable.Range(0, vertices.Count - indexOffset).Select(_ => new Color(0.5f, 0.1f, 0.1f)));

    foreach (var x in Enumerable.Range(0, (vertices.Count - indexOffset) / 4))
    {
        triangles.AddRange(new[]
        {
            0, 2, 1,
            1, 2, 3,
        }.Select(i => indexOffset + x * 4 + i));
    }
}
```

## アップルの動作

![SnapCrab_NoName_2016-12-9_19-47-31_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/e7fe41f3-a822-5262-7ce2-dcfaf168a61a.png)

# 全てを結合してメッシュを作成

```c#
Mesh mesh;

List<Vector3> vertices;
List<Color> colors;
List<int> triangles;

void Start ()
{
	mesh = new Mesh();

    vertices = new List<Vector3>();
    colors = new List<Color>();
    triangles = new List<int>();

    Pen(Matrix4x4.TRS(new Vector3(-1.75f, 0, 0), Quaternion.AngleAxis(90, Vector3.forward), Vector3.one * 0.7f));
    Pineapple(Matrix4x4.TRS(new Vector3(-0.5f, 0, 0), Quaternion.identity, Vector3.one * 0.7f));
    Apple(Matrix4x4.TRS(new Vector3(0.5f, 0, 0), Quaternion.identity, Vector3.one * 1.3f));
    Pen(Matrix4x4.TRS(new Vector3(1.35f, 0, 0), Quaternion.AngleAxis(-90, Vector3.forward), Vector3.one * 0.7f));

    mesh.SetVertices(vertices);
    mesh.SetTriangles(triangles, 0);
    mesh.SetColors(colors);
    mesh.RecalculateNormals();
    mesh.RecalculateBounds();

    GetComponent<MeshFilter>().mesh = mesh;
}
```

# 動作
![SnapCrab_NoName_2016-12-9_19-40-58_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/6968d539-1d44-d404-f5ad-25336da93f73.png)
![glsl.gif](https://qiita-image-store.s3.amazonaws.com/0/52576/73bfb86a-83a0-65e6-5838-61416a5e7c16.gif)

##一つのメッシュなのでSetPassCallも少ない！

![SnapCrab_NoName_2016-12-9_19-41-46_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/52576/e2e47c98-c6ac-d585-9f6a-ebdd64006d40.png)

## 自前でメッシュを作ってるので、シェーダーの自由度も高い！
![glsl2.gif](https://qiita-image-store.s3.amazonaws.com/0/52576/6ef75d0f-1716-6bc1-716e-6e00ea33e7dc.gif)



# まとめ

- **Mayaで作った方が早い** :innocent: 
- シェーダー沼はいいぞ！！