UnityのUI ToolkitでVisualElementで描画のためのメッシュを自由に生成できるMeshGenerationContextの使い方についてまとめました。
Unity 2022.2.19
MeshGenerationContextとは
UI ToolkitでUIを描画する際には、内部的にそのUIを描画するためのメッシュが生成されます。
この処理は通常は自動的に行われますが、VisualElement.generateVisualContent
にコールバックを登録することで、任意のメッシュを生成することができます。
この時のコールバック関数の引数として渡されるのがMeshGenerateContext
です。
このクラスにメッシュの設定することで、UI領域にそのメッシュが描画されます。
なおVisualElement.generateVisualContent
による再描画は、ビジュアルに影響する VisualElement のプロパティに変更が加わったり、VisualElement.MarkDirtyRepaint
が呼ばれると発生します。
使ってみる
では実際にこれを使って独自のメッシュを使ってUIを描画してみます。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleElement : VisualElement { public ExampleElement() { // 描画用メッシュを生成する際のコールバックを登録 generateVisualContent += OnGenerateVisualContent; } private void OnGenerateVisualContent(MeshGenerationContext context) { // 小さすぎて見えないレベルだったら描画しない if (contentRect.width < 0.01f || contentRect.height < 0.01f) return; var vertices = new Vertex[4]; var indices = new ushort[] { 0, 1, 2, 2, 3, 0 }; // アロケート var mesh = context.Allocate(4, 6); // 頂点座標を作成 { var left = 0.0f; var right = contentRect.width; var top = 0.0f; var bottom = contentRect.height; vertices[0].position = new Vector3(left, bottom, Vertex.nearZ); vertices[1].position = new Vector3(left, top, Vertex.nearZ); vertices[2].position = new Vector3(right, top, Vertex.nearZ); vertices[3].position = new Vector3(right, bottom, Vertex.nearZ); } // 頂点カラーを作成 { vertices[0].tint = Color.red; vertices[1].tint = Color.green; vertices[2].tint = Color.blue; vertices[3].tint = Color.black; } // 頂点インデックスを作成 { indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 2; indices[4] = 3; indices[5] = 0; } // 頂点と頂点インデックスを設定 mesh.SetAllVertices(vertices); mesh.SetAllIndices(indices); } }
説明はコメントに書いたとおりです。
わかりやすさ重視のコードにしていますが、再生成を考えると、本当はもう少し効率化(アロケーションしないように)すべきです。
動作確認
さて次にこのUIの描画結果を確認するために、以下のようにEditor Windowを作成します。
using UnityEditor; public sealed class ExampleWindow : EditorWindow { public void CreateGUI() { var element = new ExampleElement(); element.style.width = 100; element.style.height = 100; rootVisualElement.Add(element); } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }
Window > Exampleからウィンドウを開くと、下図の結果が得られることを確認できます。
設定した頂点カラーが反映されたUIが描画されていることを確認できました。
テクスチャを使う場合には注意
MeshGenerationContext
でテクスチャを使う場合にはUV座標の設定に注意が必要です。
テクスチャは効率化のため、内部的にアトラス化される場合があるので、UV座標にはアトラスのUVを考慮した値を設定する必要があります。
これは以下のコメント部分のように、MeshGenerationContext.uvRegion
を使って行います。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleElement : VisualElement { private readonly Texture2D _texture; public ExampleElement() { generateVisualContent += OnGenerateVisualContent; // 適当にテクスチャを作る var texture = new Texture2D(2, 2); _texture = texture; texture.SetPixels(new[] { Color.white, Color.white, Color.white, Color.white }); texture.Apply(); } private void OnGenerateVisualContent(MeshGenerationContext context) { if (contentRect.width < 0.01f || contentRect.height < 0.01f) return; var vertices = new Vertex[4]; var indices = new ushort[] { 0, 1, 2, 2, 3, 0 }; var mesh = context.Allocate(4, 6, _texture); { var left = 0.0f; var right = contentRect.width; var top = 0.0f; var bottom = contentRect.height; vertices[0].position = new Vector3(left, bottom, Vertex.nearZ); vertices[1].position = new Vector3(left, top, Vertex.nearZ); vertices[2].position = new Vector3(right, top, Vertex.nearZ); vertices[3].position = new Vector3(right, bottom, Vertex.nearZ); } { vertices[0].tint = Color.red; vertices[1].tint = Color.green; vertices[2].tint = Color.blue; vertices[3].tint = Color.black; } { indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 2; indices[4] = 3; indices[5] = 0; } // UV座標を設定 { // 内部的にテクスチャがアトラスに格納される場合があるので、UVはuvRegionを使って際スケーリングする Vector2 GetScaledUv(Vector2 uv) { return uv * mesh.uvRegion.size + mesh.uvRegion.min; } vertices[0].uv = GetScaledUv(new Vector2(0, 0)); vertices[1].uv = GetScaledUv(new Vector2(0, 1)); vertices[2].uv = GetScaledUv(new Vector2(1, 1)); vertices[3].uv = GetScaledUv(new Vector2(1, 0)); } mesh.SetAllVertices(vertices); mesh.SetAllIndices(indices); } }
UV座標を設定する際にはこの点に注意してください。