ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20220206_01

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

      UnityUI
      2022-02-06

      マイケル
      マイケル
      みなさんこんばんは!
      マイケルです!!
      エレキベア
      エレキベア
      クマ〜〜〜〜
      マイケル
      マイケル
      さて、今回は 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);
          }
      }
      
      ↑Canvas Rendererも設定されていること
      マイケル
      マイケル
      するとこのようにSceneViewに三角形が描画されることが確認できます!
      ↑三角形が描画される
      マイケル
      マイケル
      スクリプトでは Graphicクラスを継承して、OnPopulateMesh()関数をオーバーライドしています。
      この関数の引数で渡される VertexHelperを操作することでメッシュを描画することができる というわけです!
      マイケル
      マイケル
      処理内容は非常に単純で、

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

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

      頂点を動的に移動させる

      マイケル
      マイケル
      三角形は描画できましたが、このままではゲーム中に座標を変更しても反映されません。
      反映させるタイミングは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を設定する
      マイケル
      マイケル
      するとこのようにうにゃうにゃ動くことが確認できました!
      ↑うにゃうにゃして面白い
      エレキベア
      エレキベア
      なんかかわいいクマ〜〜
      マイケル
      マイケル
      同じ容量で、頂点カラーも動的に変更できます。
      下記は動的にグラデーションさせてみた例です!
      ↑頂点カラーも動的に変更できる
      エレキベア
      エレキベア
      さらに美しいクマ〜〜〜

      四角形メッシュの描画

      メッシュの描画

      マイケル
      マイケル
      慣れてきたところで四角形も描画してみましょう!
      4つ頂点を指定するのはもちろんですが、三角ポリゴン単位での描画になる ことには注意です!
      ↑三角形ポリゴン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();
              }
          }
      }
      
      マイケル
      マイケル
      このスクリプトをアタッチすると、無事描画することが確認できました!
      エレキベア
      エレキベア
      やったクマ〜〜〜〜〜

      Spriteの設定

      マイケル
      マイケル
      それでは次はこの四角形メッシュにSpriteを表示させてみましょう!
      頂点4つのままでもいいですが、UnityのTextコンポーネントやImageコンポーネントは6つの頂点で定義されているみたいなので、合わせて6つの頂点を定義してみましょう。
      ↑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が描画されます!
      エレキベア
      エレキベア
      (カニクマ・・・・。)
      マイケル
      マイケル
      ちなみにメッシュは変形可能だから、下記のように分割して表示することもできるよ!
      ↑メッシュを離すことも可能
      ↑引き裂かれるカニ
      エレキベア
      エレキベア
      (カニが引き裂かれたクマ・・・。)

      アンチエイリアス処理

      マイケル
      マイケル
      ここからはおまけの内容になりますが、
      簡易的なアンチエイリアス処理をかけてみましょう!
      マイケル
      マイケル
      描画されたメッシュを拡大すると、何もしないと下記のようにギザギザになっていることが分かります。
      ↑ギザギザしている
      エレキベア
      エレキベア
      ピクセル感がすごいクマ
      マイケル
      マイケル
      これにアンチエイリアスをかけた場合は下記のようになります。
      すこしぼやけた感じですね。
      ↑アンチエイリアスをかけた例
      エレキベア
      エレキベア
      これはどうやるクマ??
      マイケル
      マイケル
      下記は三角形の例だけど、頂点の外側にもう一つ、
      頂点カラーを透明に設定した頂点を定義する
      ことで実現できるよ!
      ↑アンチエイリアス外側の頂点を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すると下記のようにうまく適用できていることが分かります!
      ↑今回はこれでいい感じにぼやけた
      エレキベア
      エレキベア
      複雑な図形だともう少し考える必要がありそうクマね
      マイケル
      マイケル
      もっといろいろ触っていきたいところだけど、
      今日の授業はここまでだ!!
      エレキベア
      エレキベア
      おつかれさまクマ〜〜〜

      おわりに

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

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

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


      UnityUI
      2022-02-06

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
      2024-04-14
      【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-30
      【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
      2024-03-30
      【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-28
      【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
      2024-03-28
      【Unity】サウンドミドルウェアに依存しない設計を考える【CRI ADX・Wwise】
      2024-03-27