2年かけてUnityでuGUIやUI Toolkitの代わりに使うGUIライブラリを開発して公開した

# はじめに
UnityのuGUIやUI Toolkitを使っていて、「もっとシンプルにコードでGUIを組みたい」と思ったため、RimGuiという**自分の理想に合ったGUIライブラリ**をゼロから開発して公開しました。

https://gridrand.com/rimgui/WebGL/

https://assetstore.unity.com/packages/tools/gui/rimgui-316805

![main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076596/13a8cde5-8235-432d-863e-c9e8fbee7a82.png)

# 自分の理想のGUI
- コードでGUIを組みやすい
- 簡単にGUIを作成できる
- 実用性重視
  - 派手さは必要ない
- デバッグや開発時だけでなく、本番のゲームでも使える
- 高速
- Unityへの依存が少ない
  - 拡張、カスタムしやすくするため
  - 実装の中身が見通しやすく、理解しやすくなる
  - パフォーマンスチューニングをしやすくなる

# 既存のGUI
uGUI
- 安定している
- コードだけで完結するUI構築は困難
- 大量のUIだと重い
- 動的な更新があると重い

UI Toolkit(ほとんど使ったことがないので的はずれかも)
- スタイリングしやすい
- ボイラープレートが多い
- スクリプトからの制御がしづらそう
- 個人的にHTMLやCSSを知らないため、あまりメリットを感じない。

Unity IMGUI
- コードだけでUIを作成できる
- runtimeで使うにはパフォーマンスが厳しい
- 複数解像度への対応が難しい

# 開発したGUIライブラリ
Immediate-Mode GUIとなっており、シンプルで直感的に使えるように開発しました。
ライブラリ名は**Immediate-Mode**から**Im**を取り、**R**を付けて**RimGui**と名付けました。

Built-In,URP,HDRPに対応しており、PCやMobile,WebGLでも動作します。

# 使い方
以下のように、メソッドを呼ぶだけでGUIを構築できます。

```csharp
Gui.Heading("Sample");
Gui.LabelSlider("Slider", ref value, 0, 100);
if (Gui.Button("Increment"))
    value++;
```

![sample-ui.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076596/098ca398-6bc1-4c39-9957-cf131c35c07e.png)

- `Heading()`：見出しを表示
- `LabelSlider()`：ラベル付きスライダーを表示し、値を制御
- `Button()`：ボタンを表示し、クリックされたら`true`を返す
  - Dear ImGuiなどの多くのImmediate-Mode GUIと同じ

他にも多くのWidgetや機能が存在します。
- Box  
- Button  
- ToggleButton  
- RadioButton  
- CheckBox  
- Color  
- ColorPicker
- Custom
- Dropdown  
- Foldout  
- Frame
- InputText  
- Label  
  - LabelText, LabelInput, LabelNumeric, and more
- ListView  
- Scroll  
- Separator  
- SlidePad  
- Slider  
- Sprite  
- Splitter  
- Tab  
- Text  
- Texture  
- Tree  
- Window  
- Drag and Drop  
などなど

詳しくは下のリンク先を参考にしてください。

https://gridrand.com/rimgui/docs/introduction

# フォントアトラスの生成
TextMeshProを使わず、独自にフォントアトラスの生成およびレンダリングを行っています。  
フォントのラスタライズには、`stb_truetype`のC#移植版を使用しています。

![atlas-font-editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076596/c8680910-b75f-49b3-b60e-02091678fc39.png)


Unityには [FontEngine](https://docs.unity3d.com/ja/2022.3/ScriptReference/TextCore.LowLevel.FontEngine.html) というクラスが存在しますが、重要なメソッドが`internal`となっており、使用するとクラッシュすることが多くありました。
また、ラスタライズ時の座標も不明瞭だったため採用を見送りました。

とはいえ内部では`JobSystem`を活用しており、基盤にはおそらく`FreeType`が使われていることから、高いパフォーマンスが期待できます。
そのため、クラッシュの原因や座標の仕様が明確になれば、採用も十分検討に値すると考えています。

# 描画
SetPass callsを抑えるために、Shader、Materialを1つにしています。

Unityではさまざまな手法でレンダリングが可能ですが、RimGuiでは`CommandBuffer`を使用しています。これは、特定の領域をフレームバッファから切り離して描画する必要があるためです。

また、`CommandBuffer.DrawMesh()`を用いた描画では、たとえ同じMaterialを使っていても、**Statistics**ビューでは`SetPass calls`が増加しているように見えますが、実際にはGPUへのステート変更は行われていないため、問題はありません。

>Opening up RenderDoc capture shows that, if the material is unchanged between DrawMesh calls, all that is being fired off to the GPU are calls to set vertex/index buffers and then a draw call…it looks like the profiler is wrong. I don’t think that Unity is sending state changes to the GPU unless it has to.
[Is there a way to batch meshes that are drawn with CommandBuffer\.DrawMesh? \- Unity Forum](https://discussions.unity.com/t/is-there-a-way-to-batch-meshes-that-are-drawn-with-commandbuffer-drawmesh/626427/22)


## Text
SDFかBitmapかで迷いましたが、SDFでTextを描画しています。

SDFのメリット
- 高DPIだと綺麗
  - モバイルだと特に顕著
- Fontのサイズがいくつでも1つのアトラスで対応可能
- 今後、影などのエフェクトを描画できるように改修できる

Bitmapのメリット
- ラスタライズが速い
  - ダイナミックにテクスチャアトラスに書き込む場合に重要
- Shaderがシンプル

# 謝辞
以下のライブラリは、本実装にあたって非常に参考になり、多くのインスピレーションを頂きました。
この場を借りて、心より感謝申し上げます。
- [Dear ImGui](https://github.com/ocornut/imgui)
- [egui](https://github.com/emilk/egui)
- [ZimGui](https://github.com/Akeit0/ZimGui-Unity)

# 最後に
結果として開発に2年以上かかってしまいましたが、最初はこんなにも時間をかけるつもりはありませんでした。
使いやすくするためにある程度できてから大部分を改修したため、ここまで時間が掛かってしまいましたが、使いやすいGUIライブラリになったと思います。
ただ、lineやtriangleのアンチエイリアス化など、まだ足りない機能があるため、今後も開発を続けていきます。

ご意見やご要望などありましたら、ご連絡いただけると嬉しいです。