🔣

【Unity】FontEngineで軽量テキスト描画

2023/12/08に公開

これはUnity Advent Calendar 2023 (2) の8日目の記事です。

https://qiita.com/advent-calendar/2023/unity


UnityEngine.TextCore.LowLevel.FontEngineは、TextMesh ProUI Toolkitがバックエンドとして使用している文字描画エンジンです。

その機能の多くはinternalでユーザーに公開されておらず、使える機能もかなり低レベルですが、そのかわり高速に動作します。基本的な文字描画機能のみを必要としていて、TextMesh Proだとオーバースペックである場合は、直接FontEngineを使用してみるのもありなんじゃないかと思います。

……とは言いつつ、そんな場面はかなり限定的なので、基本的にはネタ記事です。

基本的な使い方

① フォントをロードする

FontEngine.LoadFontFace()を使ってFontを読み込みます。
一度にロード状態にできるフォントは1つまでで、基本的に最後にロードしたフォントが使用されます。

using UnityEngine;
using UnityEngine.TextCore.LowLevel;

[SerializeField] private Font font;

FontEngine.Initialize();
FontEngine.LoadFontFace(font, 90); // フォントサイズ=90

② Glyphを取得する

FontEngine.TryGetGlyphWithUnicodeValue()を使って、特定の文字の情報を示すGlyph構造体を取得します。

if (!FontEngine.TryGetGlyphWithUnicodeValue(character,
    GlyphLoadFlags.LOAD_NO_BITMAP, out var glyph))
{
    // Glyphの取得に失敗した場合
}

③ テクスチャに描画する

FontEngine.RenderGlyphToTexture()を使用して、対象のテクスチャに文字を描画することができます。

RenderGlyphToTexture()internalなので、無理やり呼び出すためのコードを先に用意してしまいます。

public static class FontEngineProxy
{
    private static Func<Glyph, int, GlyphRenderMode, Texture2D, FontEngineError> dRenderGlyphToTexture;

    public static FontEngineError RenderGlyphToTexture(
        Glyph glyph,
        int padding,
        GlyphRenderMode renderMode,
        Texture2D texture)
    {
        if (dRenderGlyphToTexture == null)
        {
            var method =
                typeof(FontEngine).GetMethod("RenderGlyphToTexture", BindingFlags.Static | BindingFlags.NonPublic);
            dRenderGlyphToTexture =
                (Func<Glyph, int, GlyphRenderMode, Texture2D, FontEngineError>)Delegate.CreateDelegate(
                    typeof(Func<List<Glyph>, int, GlyphRenderMode, Texture2D, FontEngineError>),
                    null, method);
        }

        return dRenderGlyphToTexture.Invoke(glyph, padding, renderMode, texture);
    }
}

実際に描画を行うコードは次のような感じです。

using UnityEngine.TextCore;

// テクスチャ上の描画位置を指定(ピクセル単位)
glyph.glyphRect = new GlyphRect(x, y, width, height);

FontEngineProxy.RenderGlyphToTexture(glyph, 0, GlyphRenderMode.SMOOTH_HINTED, texture);

texture.Apply();

注意点として、テクスチャフォーマットはR8である必要があり、他のフォーマットだと正しく描画されません。グレースケールを前提とした実装ですね。

その他のFontEngineの機能(抜粋)

RenderGlyphsToTexture()

複数のグリフをListとして渡し、一度に描画してくれるAPIです。
特に検証はしていませんが、RenderGlyphToTexture()を繰り返し呼ぶより効率よく描画を行えそうな雰囲気があります。

TryPackGlyphInAtlas() / TryPackGlyphsInAtlas()

アトラス上の空いているスペースに文字を詰めてくれるAPIです。TextMesh Proの内部で使用されています。

つかいみち

以上でわかると思いますが、FontEngineがやってくれるのは、テクスチャに文字を描画するところまでです。また、FontEngineはCPU動作なので、毎フレームの描画よりはアトラスの作成用途が基本になると思います。

使用例

unicode上の文字を連続的にグリッド上に配置する、という機能の実装にFontEngineを使用しました。

以前は一文字ごとに別々のTMP Textを配置していましたが、スクロール時にFPSが大きく低下していました。原因はおそらく、TextMesh Proのダイナミックアトラスにおける文字のパッキング処理が1文字ずつ頻繁に発生したことにあります。
今回は文字の配置がグリッド状で、画面上に同時に表示される文字の数も固定数なので、TextMesh Proの柔軟なダイナミックアトラスシステムはオーバースペックでした。必要なアトラステクスチャのサイズは固定されていますし、文字のパッキングも一行ずつ行えばいいので、計算量の面でも有利なはずです。

ということで、使用できる場面は非常に限定的ですが、FontEngine使えると色々便利だよ、という記事でした。

Discussion