UnityでDistance Fieldを使った文字の描画をする(WIP)
Unity Advent Calendar 2015 - Qiita 12月20日の記事です。
(今年も間に合わなかったので中途半端な状態で公開します)
昨年のアドベントカレンダーでUnity UIのTextに綺麗な輪郭線を付ける(n-yoda/unity-vertex-effects · GitHub)という話を書きました。外国の方からのStarが多い気がしたので全て英語に書き換えてしまいましたが。
昨年やった方法は最も簡単な方法ですが、頂点数増えすぎだと指摘されるので、今年はもう少しまともなアプローチを試してみたいと思います。
文字の描画方法の比較検討
まともなアプローチを試す、ということで思い付く方法をいくつか列挙して、比較検討するところから初めます。輪郭線に限らず文字の描画方法ごとの利点欠点等を上げてみます。
UnityのDynamic Fontの場合
文字の描画要求がある度に、FreeTypeで文字をラスタライズしてAlpha8形式のテクスチャアトラスに詰め込んでいきます。サイズの異なる文字は使い回せませんが、色の異なる文字は使い回せます。影や輪郭線は昨年書いた方法の用に、少しずつずらして重ねることで表現出来ますが、頂点数が無駄に増えます。
FontをTextureに焼く時点で輪郭線を付ける
UnityのDynamic FontはAlpha8のテクスチャに無色の文字を書き込みますが、例えばRGBA32のテクスチャに、初めから色や輪郭線の付いた文字を書き込めば何でも表現出来ます。この場合装飾付きの文字を使い回すことはほぼ無さそうなので、例えばラベル1つに付きテクスチャも1枚生成してしまっても良さそうです。欠点はメモリ消費が多いことですが、描画はコストは他のSprite等と殆ど変わらないはず。効率の良い実装をしようとすると面倒な割に拡張性が低く、OSの機能を使うか、FreeType含め何かライブラリを使ってプラグインを作ることになりそうです。
Meshでベジェ曲線を描画する
フォントは2次か3次のベジェ曲線ですが、それを通常のグラフィックスパイプラインで良い感じに描画する手法があります。Microsoft Researchの論文が分かりやすくて良いですが、特許があるのでどうなんでしょう…。1つのMeshで描画サイズに関わらず綺麗に描画でき、メモリ消費も少なく高速そうです。影や輪郭線は上記のずらして重ねる方法に甘んじるなら簡単ですが、無駄に頂点を増やさずにやろうとすると難しそうです。
Distance Fieldを使って描画
SIGGRAPH2007のCourse(参考PDF)で紹介されている方法です。日本語による説明は以下のページを見ましょう。
この手法の利点は「Meshでベジェ曲線…」の方法と同様に、描画サイズに関わらず綺麗な描画が可能なことです。更に、少しシェーダーを工夫すれば影や輪郭線が高速に出来ます(上のリンク参照)し、センスがあればもっと凝った装飾も実装できそうです。また、Distance Fieldは異なる文字サイズでもある程度使い回すことができ、当然異なる色での使い回しも可能なので、UnityのDynamic FontのようにAlpha8のテクスチャに詰め込んで行くような使い方にも適しているかもしれません。効率的な実装はプラグインを書くことになるので面倒ですが、一度作ってしまえばシェーダーを工夫して色々遊べるので2つ目の方法より作る価値は高そうです。
結論
UnityのDynamic FontのDistance Field版があれば便利そう。
先行例の調査
- mp1mm16tir (A bitmap font contains japanese characters) https://github.com/ayamada/mplus-1mn-medium-16-fnt-tir Distance fieldの形で配布されているフォント。fnt形式の他に、よく使う日本語文字を2900文字を1枚のテクスチャに詰め込んだPNGも配布。これで十分ならDynamic Fontの動作を真似る必要は無さそう。
Disatnce Fieldの生成
ググるとDistance Map Generator等何かとヒットしますが、出来ればFontデータから直接高速にDistance Fieldを生成したいので、FreeTypeを使って実装してみました。
という予定だったのですが、まだ作りかけなので途中経過を貼っておきます。とても人に見せられるようなコードでは無いですが。必要なコードがまだ全部書けていないので部分的にDistance Fieldになっているような結果が出ます。
描画
Texture2D.LoadRawTextureDataで読み込んで一応描画もやって見ました。適当に書いた以下のシェーダー(多分間違っている)で上のDisatance Mapを大小2個表示してみました。
fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); float2 d2 = abs(ddx(i.uv) + ddy(i.uv)); float d = min(d2.x, d2.y); return saturate((col.a - _Sub) * _Mul / d); }
実際に使うのにあと何をすべきか
- とりあえずDistance Fieldの生成を完成させる。
- シェーダーを書く。
- Unityのプラグインにする。
- UnityのFontやTextGeneratorが提供しているような機能を実装する。
- Distance Fieldの生成部分のGPU実際も検討する。
などなど、多分実際に使う機会でも訪れない限り完成はしなそうです・・・。