土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

#unity 深掘りTextMeshPro:SDFフォントデータによる文字描画実装(第1回:SDFの仕組み)

 去年の夏頃にTextMeshProの実装、中でもSDF形式のフォントデータを使った文字描画処理について延々解析していました。その頃に得た知見を何回かに分けて書いて行きたいと思います。
 今回は、そもそもSDFフォントデータとはどういう物で、どういう仕組みで文字描画を行っているのかについて解説します。

注意

 今回の記事で表示されている「永」の文字は、筆者がスクラッチで作成した文字描画ルーチンで描画しています。そのためTextMeshProで描画した場合とは描画結果が異なります。ご注意ください*1

TMP_FontAssetアセット

 TextMeshPro(以下TMP)では、truetypeフォントをSDFという形式のアセット変換して使用しています。SDFは「Signed Distance Field」の略で、無理矢理日本語に訳すと「符号付き距離場」になるでしょうか。
 TMPでは、truetypeフォントをSDF形式に変換する機能が提供されており、それを使うとTMP_FontAssetというアセットを出力します。このアセットは、下図のようなグレースケールのテクスチャ画像と、各文字ごとの情報を格納したメタデータ群のセットになっています。今回はこのグレースケールテクスチャが持つ機能について解説します。

 余談ですが、TMPのSDFではRGBAテクスチャを使っていますが、Aチャンネル以外は未使用です。*2

SDF(Signed Distance Field)

 SDFでは、各文字ごとに、その文字を構成する線の「フチ」からの「距離」を計算し、ビットマップデータに格納しています。そして、文字を画面に描画する時は、この距離情報を閾値として使う事で、文字の太さを変えたり、アウトラインを描画したりしています。

 距離情報は以下の様に保存されています。下図は、ある文字の辺の先端を拡大した物をイメージしています。左図が元のフォント、右図がSDF形式に変換した物です。

 元のフォントのフチにあたるドットを0.5とし、フチの外(つまり、文字領域外)に行くほど値を小さく(最小0.0)、フチの中(つまり、文字領域内)に行くほど値を大きく(最大1.0)し、全てのドットについて0.0~1.0の値を設定します。
 このように、ピクセル毎に文字のフチからの「距離」を記録するので「符号付き距離場」という訳です。テクスチャとして格納する都合上0.0~1.0の値になっていますが、本来(?)は、文字のフチを0.0として、外側がマイナス、内側がプラスの-1.0~0.0~1.0になるので「符号付き」なのだと思われます。

 余談ですが、この距離計算は、簡単に言えば「各ドットについてその周囲のドットを走査し、文字があればそこまでの距離を計算する」という感じで行っています。TMPではSDF変換時に、サンプルとして使うフォントのサイズや、この距離計算の深さや細かさを設定する事ができます。←(2024/1/26追記:すみませんここ何か別の実装と勘違いしていました。TMPのジェネレーターはこのような実装になっていないようです。これについては別途記事を立てる予定です)

距離値を用いた文字描画

 さて、文字を描画する時には、このテクスチャをサンプリングするわけですが、この時に距離情報を閾値として使用することで、文字を加工した上で描画が出来るのです。
 幾つか例を見てみましょう。以下、テクスチャからサンプリングした値を「距離値」と呼称します。

 「サンプリングした距離値が0.5以上であれば常に描画対象とする」とした場合、元のフォントと同じ形式で描画できます。

 値の下限を小さくすると、フチの外のドットも描画対象として採用することになり、文字を太くできます。

 逆に、値を大きくすると文字が細くなります。

 注意ですが、サンプリングした値はあくまで距離値として使用され、描画するピクセルのカラー値には関係しません。ユーザーは任意のカラー値で文字を描画出来ます。

 距離値に応じて描画するカラー値を切り替える事で、アウトライン文字(袋文字)を描画できます。

 距離値はフチの内外で線形に変化するので、この値をアルファ値に適用することで、文字のフチにアンチエリアスをかける事もできます。

 ちなみに、アウトラインを適用した時は、距離値を参照してアウトラインと本体のカラー値をブレンドする事も出来ます。

 アウトライン文字に、カラー値のブレンドとアンチエイリアスの両方を適用するとこんな感じ(わかりやすさの為に、前の図とはアウトラインの太さを変えています)。

おわりに

 このように、SDFデータを使うと、文字描画時に様々な処理を付加できるようになります。この形式は描画時の拡大縮小にもそれなりに強く、また、距離値を用いた処理は高速に実行できるため、ゲームの様なリアルタイム向きの技術と言えるでしょう。
 SDFには弱点がいくつかあり、特に漢字圏ではそのまま使うのは正直難しいんじゃないかと個人的には思っているのですが、それについては回を追って書ければと思います。

 次回は、このSDFデータを使って実際に文字描画を行うシェーダーコードを解説します。

宣伝

拙著同人誌「Unityシェーダープログラミングの教科書5 SRP[2]UniversalRP URP拡張カメラ/HDR/ポストプロセス編」BOOTHでPDF版を頒布中です。
s-games.booth.pm
既刊1~4巻についてもよろしくお願いします。
s-games.booth.pm


*1:SDFフォントデータ自体はTMPで生成した物です

*2:SDFには様々な形式が提案されていて、他チャンネルにデータを格納する物もあります