【Unity】uGUI上でメッシュを動的に描画する

Unity
マイケル
マイケル
みなさんこんばんは!
マイケルです!!
エレキベア
エレキベア
クマ〜〜〜〜
マイケル
マイケル
さて、今回は Unityでスクリプトから動的にメッシュを作成する方法
について紹介していきます!
エレキベア
エレキベア
形を変えたりできるクマ??
動的に作成するとどんなメリットがあるクマ?
マイケル
マイケル
頂点単位で操作できるようになるから、
スクリプトからメッシュを自由に量産できるし、
ゲーム中に変形することもできる んだ!
エレキベア
エレキベア
自由自在にメッシュを操作できるなんて夢が広がるクマね
マイケル
マイケル
それとペイント処理なんかにも使えるし、
透明部分が多い画像等は、必要な部分だけメッシュ描画することで
パーフォーマンスもよくなるみたいだよ。
マイケル
マイケル
今回は簡単な図形の描画とアレンジを加えながら
メッシュ操作に慣れていこう!
エレキベア
エレキベア
やったるクマ〜〜〜

スポンサーリンク

三角形メッシュの描画

メッシュの描画

マイケル
マイケル
まずは一番簡単な三角形メッシュの描画から!
以下のようなスクリプトを記述してCanvas内の適当なGameObjectにアタッチ しましょう!
using System;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 三角形Mesh
/// </summary>
public class TriangleMesh : Graphic
{
    /// <summary>
    /// 頂点情報
    /// </summary>
    [Serializable]
    private struct VertexInfo
    {
        public Vector2 position; // 位置
        public Color32 color;    // カラー
    }
    [SerializeField] private VertexInfo[] vertexInfos = new VertexInfo[]
    {
        new VertexInfo
        {
            position = new Vector2(0.0f, 100.0f),
            color = Color.white
        },
        new VertexInfo
        {
            position = new Vector2(100.0f, -100.0f),
            color = Color.white
        },
        new VertexInfo
        {
            position = new Vector2(-100.0f, -100.0f),
            color = Color.white
        }
    };

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        if (vertexInfos?.Length != 3)
        {
            Debug.LogError("TriangleMesh => 頂点が3つ設定されてないよ");
            return;
        }
        
        // 頂点情報をクリア
        vh.Clear();

        // 頂点座標を設定
        for (var i = 0; i < vertexInfos.Length; i++)
        {
            var v = new UIVertex
            {
                position = vertexInfos[i].position,
                color = vertexInfos[i].color
            };
            vh.AddVert(v);
        }

        // 三角形を描画
        vh.AddTriangle(0, 1, 2);
    }
}
ScreenShot 2022 02 06 19 07 05↑Canvas Rendererも設定されていること
マイケル
マイケル
するとこのようにSceneViewに三角形が描画されることが確認できます!
ScreenShot 2022 02 06 19 06 25↑三角形が描画される
マイケル
マイケル
スクリプトでは Graphicクラスを継承して、OnPopulateMesh()関数をオーバーライドしています。
この関数の引数で渡される VertexHelperを操作することでメッシュを描画することができる というわけです!
マイケル
マイケル
処理内容は非常に単純で、

・VertexHelper.AddVert() で3つの頂点座標を定義して
・VertexHelper.vh.AddTriangle() で描画するインデックス座標を指定する

という手順だけで描画することができます。
ScreenShot 2022 02 06 19 49 32↑ワイヤフレーム表示したもの。3つの座標を定義してインデックス座標で描画する
エレキベア
エレキベア
三角形だと分かりやすいクマね
マイケル
マイケル
頂点座標やインデックス座標について知らない方は、
以前座標変換についての記事も書いているのでこちらもご覧ください・・・。
エレキベア
エレキベア
なつかしクマ〜〜〜
マイケル
マイケル
またこの時、頂点ごとにカラーを設定することもできます。
下記はそれぞれ赤、青、緑を指定した例です。
ScreenShot 2022 02 06 19 20 16↑頂点ごとにカラーも設定できる
エレキベア
エレキベア
これは綺麗クマね

頂点を動的に移動させる

マイケル
マイケル
三角形は描画できましたが、このままではゲーム中に座標を変更しても反映されません。
反映させるタイミングはUpdate内で SetVerticesDirty()を呼び出すことで指定 することができます。

    private const float VertexUpdateDuration = 0.05f; // 頂点を更新する間隔
    private float _time = 0.0f;
    private void Update()
    {
        // 一定間隔で頂点情報を再更新する
        _time += Time.deltaTime;
        if (_time > VertexUpdateDuration)
        {
            _time = 0.0f;
            SetVerticesDirty();
        }
    }
↑一定間隔でダーティフラグを設定する
マイケル
マイケル
Dirtyは汚い、古いという意味で、状態が古くなったことを伝える処理 になります。
これはダーティフラグというデザインパターンが使用されている例になりますね。
エレキベア
エレキベア
ダーティフラグはちまちま見たことあるクマ〜〜
マイケル
マイケル
それでは更新されていることが分かるよう、各頂点の座標をランダムに動かしてみましょう!
        // 頂点座標を設定
        var totalPos = Vector2.zero;
        for (var i = 0; i < vertexInfos.Length; i++)
        {
            // ランダムにoffset値を設定する
            var randomOffset = new Vector2(Random.Range(-5.0f, 5.0f), Random.Range(-5.0f, 5.0f));
            var v = new UIVertex
            {
                position = vertexInfos[i].position + randomOffset,
                color = vertexInfos[i].color
            };
            vh.AddVert(v);

            totalPos += vertexInfos[i].position;
        }
↑乱数でランダムにoffsetを設定する
マイケル
マイケル
するとこのようにうにゃうにゃ動くことが確認できました!
01 shake↑うにゃうにゃして面白い
エレキベア
エレキベア
なんかかわいいクマ〜〜
マイケル
マイケル
同じ容量で、頂点カラーも動的に変更できます。
下記は動的にグラデーションさせてみた例です!
03 gradiation↑頂点カラーも動的に変更できる
エレキベア
エレキベア
さらに美しいクマ〜〜〜

四角形メッシュの描画

メッシュの描画

マイケル
マイケル
慣れてきたところで四角形も描画してみましょう!
4つ頂点を指定するのはもちろんですが、三角ポリゴン単位での描画になる ことには注意です!
ScreenShot 2022 02 06 19 49 44↑三角形ポリゴン2つで描画する
エレキベア
エレキベア
これもC++で開発した時はやってたクマね〜〜
マイケル
マイケル
この場合は、(0,1,2)、(3,2,1)という2つのインデックス座標を定義することになります。
これを記述したスクリプトを見てみましょう!
using System;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 四角形Mesh
/// </summary>
public class SquareMesh : Graphic
{
    /// <summary>
    /// 頂点情報
    /// </summary>
    [Serializable]
    private struct VertexInfo
    {
        public Vector2 position; // 位置
        public Color32 color;    // カラー
    }
    [SerializeField] private VertexInfo[] vertexInfos = new VertexInfo[]
    {
        new VertexInfo
        {
            position = new Vector2(-100.0f, 100.0f),
            color = Color.white
        },
        new VertexInfo
        {
            position = new Vector2(100.0f, 100.0f),
            color = Color.white
        },
        new VertexInfo
        {
            position = new Vector2(-100.0f, -100.0f),
            color = Color.white
        },
        new VertexInfo
        {
            position = new Vector2(100.0f, -100.0f),
            color = Color.white,
        }
    };

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        if (vertexInfos?.Length != 4)
        {
            Debug.LogError("SquareMesh => 頂点が4つ設定されてないよ");
            return;
        }
        
        // 頂点情報をクリア
        vh.Clear();

        // 頂点座標を設定
        for (var i = 0; i < vertexInfos.Length; i++)
        {
            var v = new UIVertex
            {
                position = vertexInfos[i].position,
                color = vertexInfos[i].color
            };
            vh.AddVert(v);
        }

        // 四角形を描画
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(3, 2, 1);
    }
    
    private const float VertexUpdateDuration = 0.05f;
    private float _time = 0.0f;
    private void Update()
    {
        _time += Time.deltaTime;
        if (_time > VertexUpdateDuration)
        {
            _time = 0.0f;
            SetVerticesDirty();
        }
    }
}
マイケル
マイケル
このスクリプトをアタッチすると、無事描画することが確認できました!
ScreenShot 2022 02 06 19 10 25
エレキベア
エレキベア
やったクマ〜〜〜〜〜

Spriteの設定

マイケル
マイケル
それでは次はこの四角形メッシュにSpriteを表示させてみましょう!
頂点4つのままでもいいですが、UnityのTextコンポーネントやImageコンポーネントは6つの頂点で定義されているみたいなので、合わせて6つの頂点を定義してみましょう。
ScreenShot 2022 02 06 19 15 24↑6つの頂点として設定することもできる
エレキベア
エレキベア
なんでそれぞれ定義されてるクマ?
マイケル
マイケル
これは恐らく同じ頂点座標でも法線、UV座標が異なることがあるからじゃないかな。
下記の記事であたった問題だね・・・。
エレキベア
エレキベア
懐かしいクマ・・・・
マイケル
マイケル
スクリプトは下記の通り!
spriteの設定は、mainTextureをオーバーライドして設定し、頂点情報にはuv座標も渡してあげましょう!
using System;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// Sprite設定用Mesh
/// </summary>
public class SquareSpriteMesh : Graphic
{
    /// <summary>
    /// 頂点情報
    /// </summary>
    [Serializable]
    private struct VertexInfo
    {
        public Vector2 position; // 位置
        public Color32 color;    // カラー
        public Vector2 uv;       // uv座標
    }
    [SerializeField] private VertexInfo[] vertexInfos = new VertexInfo[]
    {
        new VertexInfo
        {
            position = new Vector2(-100.0f, 100.0f),
            color = Color.white,
            uv = new Vector2(0.0f, 1.0f)
        },
        new VertexInfo
        {
            position = new Vector2(100.0f, 100.0f),
            color = Color.white,
            uv = new Vector2(1.0f, 1.0f)
        },
        new VertexInfo
        {
            position = new Vector2(-100.0f, -100.0f),
            color = Color.white,
            uv = new Vector2(0.0f, 0.0f)
        },
        new VertexInfo
        {
            position = new Vector2(-100.0f, -100.0f),
            color = Color.white,
            uv = new Vector2(0.0f, 0.0f)
        },
        new VertexInfo
        {
            position = new Vector2(100.0f, 100.0f),
            color = Color.white,
            uv = new Vector2(1.0f, 1.0f)
        },
        new VertexInfo
        {
            position = new Vector2(100.0f, -100.0f),
            color = Color.white,
            uv = new Vector2(1.0f, 0.0f)
        },
    };
    [SerializeField] private Sprite sprite;
    
    // spriteが設定されていればmainTextureとして設定
    public override Texture mainTexture
    {
        get
        {
            if (sprite != null)
            {
                return sprite.texture;
            }
            return base.mainTexture;
        }
    }

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        if (vertexInfos?.Length != 6)
        {
            Debug.LogError("SquareSpriteMesh => 頂点が6つ設定されてないよ");
            return;
        }
        
        // 頂点情報をクリア
        vh.Clear();

        // 頂点座標を設定
        for (var i = 0; i < vertexInfos.Length; i++)
        {
            var v = new UIVertex
            {
                position = vertexInfos[i].position,
                color = vertexInfos[i].color,
                uv0 = vertexInfos[i].uv
            };
            vh.AddVert(v);
        }
        
        // 四角形を描画
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(3, 4, 5);
    }
    
    private const float VertexUpdateDuration = 0.05f; // 頂点を更新する間隔
    private float _time = 0.0f;
    private void Update()
    {
        // 一定間隔で頂点情報を再更新する
        _time += Time.deltaTime;
        if (_time > VertexUpdateDuration)
        {
            _time = 0.0f;
            SetVerticesDirty();
        }
    }
}
マイケル
マイケル
すると下記のようにSpriteが描画されます!
ScreenShot 2022 02 06 19 15 17
エレキベア
エレキベア
(カニクマ・・・・。)
マイケル
マイケル
ちなみにメッシュは変形可能だから、下記のように分割して表示することもできるよ!
ScreenShot 2022 02 06 19 51 13↑メッシュを離すことも可能
ScreenShot 2022 02 06 19 18 15↑引き裂かれるカニ
エレキベア
エレキベア
(カニが引き裂かれたクマ・・・。)

アンチエイリアス処理

マイケル
マイケル
ここからはおまけの内容になりますが、
簡易的なアンチエイリアス処理をかけてみましょう!
マイケル
マイケル
描画されたメッシュを拡大すると、何もしないと下記のようにギザギザになっていることが分かります。
ScreenShot 2022 02 06 19 25 29↑ギザギザしている
エレキベア
エレキベア
ピクセル感がすごいクマ
マイケル
マイケル
これにアンチエイリアスをかけた場合は下記のようになります。
すこしぼやけた感じですね。
ScreenShot 2022 02 06 19 25 36↑アンチエイリアスをかけた例
エレキベア
エレキベア
これはどうやるクマ??
マイケル
マイケル
下記は三角形の例だけど、頂点の外側にもう一つ、
頂点カラーを透明に設定した頂点を定義する
ことで実現できるよ!
ScreenShot 2022 02 06 19 26 46↑アンチエイリアス外側の頂点をalpha=0で定義し繋げる
マイケル
マイケル
この時アンチエイリアスのエリアは四角形になるため、三角形2つずつで辺を描くことには注意しよう!
下記のような処理をスクリプトに追加してあげると適用できます!
    [SerializeField] private bool isAntiAlias;            // アンチエイリアスを行うか?
    [SerializeField] private float antialiasWidth = 5.0f; // アンチエイリアスの幅

    protected override void OnPopulateMesh(VertexHelper vh)
    {

・・・略・・・
        
        // アンチエイリアス処理
        if (isAntiAlias)
        {
            // 外枠の頂点を追加
            var centerPos = totalPos / vertexInfos.Length;
            for (var i = 0; i < vertexInfos.Length; i++)
            {
                // 外枠の頂点位置を求める
                var vertPosition = vertexInfos[i].position + randomOffsets[i];
                var outPosition = antialiasWidth * (vertPosition - centerPos).normalized + vertPosition;
                
                // 頂点カラーの透明度を0に設定
                var outColor = vertexInfos[i].color;
                outColor.a = 0;
                
                var outerVertex = new UIVertex
                {
                    position = outPosition,
                    color = outColor
                };
                vh.AddVert(outerVertex);
            }
        
            // アンチエイリアス部分を描画
            var vi = vertexInfos.Length - 1;
            vh.AddTriangle(   0, vi+1, vi+2);
            vh.AddTriangle(vi+2,    1,    0);
            
            vh.AddTriangle(   1, vi+2, vi+3);
            vh.AddTriangle(vi+3,    2,    1);
            
            vh.AddTriangle(   2, vi+3, vi+1);
            vh.AddTriangle(vi+1,    0,    2);
        }
    }
マイケル
マイケル
外側の頂点は、
中心座標から頂点座標の正規化ベクトル * アンチエイリアスをかける幅
で設定してみました!
ON、OFFすると下記のようにうまく適用できていることが分かります!
02 antiarias↑今回はこれでいい感じにぼやけた
エレキベア
エレキベア
複雑な図形だともう少し考える必要がありそうクマね
マイケル
マイケル
もっといろいろ触っていきたいところだけど、
今日の授業はここまでだ!!
エレキベア
エレキベア
おつかれさまクマ〜〜〜

おわりに

マイケル
マイケル
さあ、今日の成果はこれだーー!!!
04 complete↑暴れ狂う三角形
エレキベア
エレキベア
(なんだこれクマ・・・)
マイケル
マイケル
日曜日がこれを作るだけで終わってしまった・・・。
悲しいけど、今後きっと役に立つはず・・!!
エレキベア
エレキベア
まあ汎用性はありそうクマね
マイケル
マイケル
また今後、複雑なことにも挑戦していこう!
それでは今日はこの辺で!
アデューー!!
エレキベア
エレキベア
クマ〜〜〜〜

【Unity】uGUI上に動的なメッシュを描画する 〜完〜

[参考文献]
Unityスクリプトリファレンス
【超基本uGUI編】Unity 動的にメッシュを生成してゴニョゴニョする
【Unity】動的に枠を描く話 – KAYAC enfineers’ blog

Unity
スポンサーリンク
この記事をシェアしよう!
フォローお待ちしています!
都会のエレキベア

コメント