夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

UnityC#でキューブを作成しUVについて理解する

本日はMRMT枠です。

今回はUV情報をスクリプトベースでメッシュに組み込む方法を調べていきます。

〇UnityC#でUVを含むメッシュを生成する

C#で頂点情報に加えUV情報を基にメッシュを構築する方法は次のようになります。

using System.Collections.Generic;
using UnityEngine;

public class CubeMeshGenerator : MonoBehaviour
{
    // メッシュの頂点座標
    private Vector3[] vertices = new Vector3[]
    {
        // 前面 (Front)
        new Vector3(-0.5f, -0.5f, 0.5f),   // 左下
        new Vector3(0.5f, -0.5f, 0.5f),    // 右下
        new Vector3(0.5f, 0.5f, 0.5f),     // 右上
        new Vector3(-0.5f, 0.5f, 0.5f),    // 左上

        // 後面 (Back)
        new Vector3(0.5f, -0.5f, -0.5f),   // 右下
        new Vector3(-0.5f, -0.5f, -0.5f),  // 左下
        new Vector3(-0.5f, 0.5f, -0.5f),   // 左上
        new Vector3(0.5f, 0.5f, -0.5f)     // 右上
    };

    // メッシュの三角形インデックス
    private int[] triangles = new int[]
    {
        // 前面 (Front)
        0, 1, 2,
        2, 3, 0,

        // 後面 (Back)
        4, 5, 6,
        6, 7, 4,

        // 左側 (Left)
        5, 0, 3,
        3, 6, 5,

        // 右側 (Right)
        1, 4, 7,
        7, 2, 1,

        // 上面 (Top)
        3, 2, 7,
        7, 6, 3,

        // 底面 (Bottom)
        0, 5, 4,
        4, 1, 0
    };

    // メッシュのUV座標
    private Vector2[] uvs = new Vector2[]
    {
        // 前面 (Front)
        new Vector2(0f, 0f),
        new Vector2(1f, 0f),
        new Vector2(1f, 1f),
        new Vector2(0f, 1f),

        // 後面 (Back)
        new Vector2(1f, 0f),
        new Vector2(0f, 0f),
        new Vector2(0f, 1f),
        new Vector2(1f, 1f)
    };

    private void Start()
    {
        // メッシュを作成して割り当てる
        Mesh mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.uv = uvs;

        // メッシュを表示するオブジェクトに割り当てる
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
        meshFilter.mesh = mesh;
    }
}

頂点の配列とUVの配列していることを確認しMeshクラスのuvにデータを代入することでuvを持ったメッシュを生成できます。

例えば上記の例の場合は最初の頂点であるVector3(-0.5f, -0.5f, 0.5f)に対応するUVはVector2(0f, 0f)となります。

上記のコードを実行した場合前面と背面にUVが持たれており、ほかの面にUV情報を持たせるためにはUV情報を与える必要があります。

しかし頂点数とUV数は一致している必要があるため面ごとにUVを追加した場合はOutOfRangeのエラーが出てしまいます。

では一度UVと頂点と面について関係を負荷彫りしていきます。

〇UV情報と頂点情報の関連

UV情報は一般的に頂点1つに対して1つ(U,Vの2次元ベクトル)のデータが格納されます。

しかし実際のUVはBlenderのUVEditorを見るとわかるのですが、各面に依存しており、面をシェアしている頂点で複数のUVを持つようなことになります。

つまり実際のUVは頂点ではなく面に依存しています。

これを処理するためには次のようにfor文でcubeTrianglesの数=メッシュのインデックスの数実行します。

この時、Unityではメッシュを三角ポリゴンとして処理するため3の倍数でfor文を回していきます。

正しくUVと頂点を適応するには次のような処理になります。

 // UV座標を計算して設定
        for (int i = 0; i < cubeTriangles.Length; i += 3)
        {
    //頂点とインデックスの対応 =メッシュの作成
            Vector3 v0 = cubeVertices[cubeTriangles[i]];
            Vector3 v1 = cubeVertices[cubeTriangles[i + 1]];
            Vector3 v2 = cubeVertices[cubeTriangles[i + 2]];

   //メッシュの法線を取得
            Vector3 normal = Vector3.Cross(v1 - v0, v2 - v0).normalized;

           //それぞれの頂点に格納されているuv情報
            Vector2 uv0 = new Vector2(0f, 0f);
            Vector2 uv1 = new Vector2(1f, 0f);
            Vector2 uv2 = new Vector2(1f, 1f);

            // List<Vector2> uvs = new List<Vector2>()のリストに追加
            uvs.Add(uv0);
            uvs.Add(uv1);
            uvs.Add(uv2);

 
            vertices.Add(v0);
            vertices.Add(v1);
            vertices.Add(v2);

            triangles.Add(i);
            triangles.Add(i + 1);
            triangles.Add(i + 2);
        }

  //メッシュにuvを適応
        mesh.uv = uvs.ToArray();

これを実行すると次のように6面すべてにUVが適応され画像が張られるようになります。

以上でC#でUVが適応されるキューブを生成できました。

〇コード一覧

今回使用したコードは次のようになります。

using System.Collections.Generic;
using UnityEngine;

public class CubeMeshGenerator : MonoBehaviour
{
    private void Start()
    {
        Mesh mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;

        List<Vector3> vertices = new List<Vector3>();
        List<int> triangles = new List<int>();
        List<Vector2> uvs = new List<Vector2>();

        // 頂点座標
        Vector3[] cubeVertices = new Vector3[]
        {
            new Vector3(-0.5f, -0.5f, 0.5f),
            new Vector3(0.5f, -0.5f, 0.5f),
            new Vector3(0.5f, 0.5f, 0.5f),
            new Vector3(-0.5f, 0.5f, 0.5f),
            new Vector3(0.5f, -0.5f, -0.5f),
            new Vector3(-0.5f, -0.5f, -0.5f),
            new Vector3(-0.5f, 0.5f, -0.5f),
            new Vector3(0.5f, 0.5f, -0.5f)
        };

        // 三角形インデックス
        int[] cubeTriangles = new int[]
        {
            0, 1, 2, 2, 3, 0,
            4, 5, 6, 6, 7, 4,
            5, 0, 3, 3, 6, 5,
            1, 4, 7, 7, 2, 1,
            3, 2, 7, 7, 6, 3,
            5, 4, 1, 1, 0, 5
        };

        // UV座標を計算して設定
        for (int i = 0; i < cubeTriangles.Length; i += 3)
        {
            Vector3 v0 = cubeVertices[cubeTriangles[i]];
            Vector3 v1 = cubeVertices[cubeTriangles[i + 1]];
            Vector3 v2 = cubeVertices[cubeTriangles[i + 2]];

            Vector3 normal = Vector3.Cross(v1 - v0, v2 - v0).normalized;

            Vector2 uv0 = new Vector2(0f, 0f);
            Vector2 uv1 = new Vector2(1f, 0f);
            Vector2 uv2 = new Vector2(1f, 1f);

            uvs.Add(uv0);
            uvs.Add(uv1);
            uvs.Add(uv2);

            vertices.Add(v0);
            vertices.Add(v1);
            vertices.Add(v2);

            triangles.Add(i);
            triangles.Add(i + 1);
            triangles.Add(i + 2);
        }

        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.uv = uvs.ToArray();
    }
}

なお上記コードでわかるように頂点数が元から増える形となり、これによって頂点数=uv数が守られるようになっています。