UnityでUIアプリ:図形描画、線描画

UnityでUI中心のアプリを作っています。
UIアプリで図形を描画する必要があることは少ないかもしれませんが、私のアプリの「プログラミングブロック」の場合はそれが必要でした。単に線を描きたかったのですが、その方法に試行錯誤しましたので一応以下に記載したいと思います。

いくつかの方法

Unityで図形を描画するにはいくつかの方法がありどの方法を採用するかを悩んだので各方法を説明します。もちろんアセットを別途導入する方法は他にあるんでしょうが、ここではUnity単体でできる方法を記載します。

VertexHelper
 Graphic コンポーネント の OnPopulateMesh イベント内で VertexHelper を使って描画します。
 最終的にはこの方法を採用しました。詳細は後述します。
 頂点(Vertexという)を登録、その後登録した複数の頂点から3つを選択して三角形を描くという処理が基本です。
 たくさんの図形を描く場合は、
 ・(3つ頂点を登録→三角形描画する)を繰り返す
 ・まずたくさん頂点を登録→ 三角形を描画を繰り返す
 のどちらの方法をとっても構いません。

LineRenderer
 LineRenderer という描画用コンポーネントを使って描画します。
 空のGameObjectに LineRendererコンポーネントをアタッチして線を描画します。線の太さも指定可能です。
 但し、
 ・ひとつのLineRendererには一筆書きで一つしか図形を描画できません。
  独立した線を複数描くということができません。
 ・ひとつのGameObjectにはひとつのLineRendererしかアタッチできません。
 よってどういう場合に使うのかよくわかりません。例えば複数の線を描画したい場合は、ひとつの線ごとにひとつのGameObjectが必要になります。一筆書きで描く図形ばかりなら使えるとは思いますが。

GLクラス
 OpenGL で直接描画するクラスとのことで、staticなプロパティとメソッドの集まりです。
 GL.BeginとGL.Endというメソッドの間で描画処理を実行します。GL.Beginで描画するモードを指定します。
 このとき、GL.LINES と指定すると線が描けるのですがこれは線の太さが指定できません。線の太さを自在にしたい場合は, GL.QUADS を指定して四角形を描きます。なぜ線を描くのに四角形を描くのかと初めは理解できませんでしたが、Unity ではほぼこれが当たり前のようです( LineRendererを除く)。確かにある太さの直線というのは、四角形には違いありません。
 この描画方法だとOnRenderObjectとかで自前で描画するものは全部描画しなおす必要があります。これがパフォーマンス的に大丈夫か不安で採用できませんでした。

Graphics.DrawMeshまたはGraphics.DrawMeshNow
 こちらは線を描くというよりもオブジェクトを描画する感じらしく、たくさんの物体を描画したいけどGameObjectを個々に作るとオーバーヘッドが大きくなるので直接物体イメージだけ描画するという場合に使うようです。
 Graphics.DrawMeshは Update() の中で使い、その後のレンダリング時に Unity が物体として描画してくれるらしいです。DrawMeshNow は描画そのものをするようで、Update() ではなくて OnRenderObject() または OnPostRender() で使うようです。
 これはなんとなくやりたいこととは違うかな、という感じで使ってません。

ということであまり明確な理由があったわけではありませんが、UIアプリの中で描画したいという感じから VertexHelper がいいかなという感じで採用しました。

VertexHelper での描画

VertexHelperによる描画は、Graphicコンポーネントの OnPopulateMeshメソッドの引数で渡ってくる VertexHelperという引数を使います。簡単なサンプルは下記です。

using UnityEngine;
using UnityEngine.UI;

public class VertexHelperSample : MaskableGraphic
{
    // Start is called before the first frame update
    public void redraw() {
        SetAllDirty();
    }
    protected override void OnPopulateMesh(VertexHelper vh) {
        vh.Clear();         // それまで描画したのはクリアして再描画する
        UIVertex vertex = UIVertex.simpleVert;  // simpleVert は標準的なVertex
        vertex.color = Color.red;
        vertex.position = new Vector2(0, 0);
        vh.AddVert(vertex);             // vertex登録。
        vertex.position = new Vector2(200, 0);
        vh.AddVert(vertex);
        vertex.position = new Vector2(0, 200);
        vh.AddVert(vertex);
        vh.AddTriangle(0, 1, 2);        // vertexの番号は登録順(0はじまり)
    }
}

表示手順は、

  1. Canvas に空の GameObejct を 作成
  2. 上記スクリプトを GameObject へアタッチ(Add Component)
  3. Canvas Renderer も GameObject へアタッチ
  4. 実行

になります。
上記スクリプトはGraphic コンポーネントを継承して OnPopulateMesh() 内で描画しています。スクリプトの処理は、

  1. VertexHelper.Clear() で全クリア
  2. 複数個(3つ以上)の頂点(Vertex)を登録
  3. 頂点(Vertex)を3つ選択して三角形を描画

という流れです。たくさんの図形を描くには 2〜3 を繰り返す、または 2でまとめて頂点を登録し 3を繰り返す。
という手順です。たくさんの図形を描くためのAddUIVertexStream、AddUIVertexTriangleStreamといったメソッドもあるようですが、こちらは使ったことがないのでよくわかりません。
OnPopulateMesh() は一度描画してしまえば再度呼び出されないので、図形を変えない場合はそのままでOKです。
図形を変えたり追加したりする場合は、Graphic.SetAllDirty()を呼び出せば OnPopulateMesh() が呼び出されるので、そこで再描画します。(上のサンプルソースの redraw() メソッド参照。)

VertexHelper (Graphic)で描画する場合の注意事項は下記のとおりです。

  • 図形の変更や追加をする場合、一つのGraphicコンポーネント内の図形をOnPopulateMesh() ですべて再描画します。一部だけを更新したり追加したりすることはできないと思います。
    再描画するにはGraphic.SetAllDirty() を呼び出します。すると OnPopulateMesh() が呼び出されます。
    SetAllDirty() は対象のGraphicコンポーネントの全図形を再描画要(Dirty)とするもののようで、一部だけ Dirty にするメソッドはないと思います。
  • 頂点(Vertex)数は最大65,000個です。これ以上図形を描きたい場合は、別のGameObject(上記のGraphicコンポーネント付き)を作る必要があります。
    最初はなんでこんな時代遅れな制限があるんだろうと思いましたが、単に一つの図形を追加するだけでもGraphicコンポーネント内の全図形を再描画するため、一つのGameObjectでは最大1000頂点程度にして GameObject を増やしていく方がパフォーマンス的には良いかもしれません。
  • 前述の方式のみでは 図形は GameObject の RectTransform をはみ出してしまいます。描画した図形がはみ出さないようにマスクするには別のGameObjectを追加し、図形を描くためのGameObjectの親にするとともにRectMask2Dコンポーネントをアタッチします。(RectMask2D が効くように、上のサンプルは実は GraphicコンポーネントではなくMaskableGraphicコンポーネントを継承しています。)

Unityでの図形描画

Unityではただ直線を描くだけでも四角形を描くことになるようです。( LineRenderer は線を描けるものの線の太さが指定できません。)これはWindowsあがりのプログラマには驚きなのですが、実際にプログラムを作るとかなり高速な動作をするのでそれでOKなんでしょう。
困ったのは円を描きたい場合で、Windowsだと円を描くためのAPIも提供されてるわけですが、Unity にはそんなものは標準ではないようです。私のアプリ 「プログラミングブロック」ではたくさんの細かな線(四角形)をつなげて円にしました。(たくさんと言っても30本くらいの線にすると円っぽく見えます。)
点線を描くのはどうするんでしょう。今のところその必要はないのでよいですが想像すると恐ろしい気がします。

以上