この記事は Qt Blog の "Text Rendering in the QML Scene Graph" を翻訳したものです。
執筆: Yoann Lopes, 2011年7月15日
少し前に、Gunnar が QML Scene Graph の新機能について 説明しました 。その記事で述べられたように、新機能の一つが "Distance Field Alpha Testing" に基づいたテキスト描画の新技術です。この技術は OpenGL の全てのパワーを活用して、それまでの Qt では成し得なかったスケーラブルでサブピクセル座標、サブピクセルアンチエイリアシングなテキストをほぼノーコストで持つことが可能となります。
まずは、以下のビデオでこの技術がもたらす改善をご覧ください。
http://www.youtube.com/watch?v=M4waDSXWGGw
おそらく、ほとんどの人が "Distance Filed"(距離場) とはなんだろうと疑問に思っていらっしゃるでしょう。Distance Transform や Distance Map とも呼ばれていますが、デジタル画像の各画素にグリフの一番近いエッジまでの距離を示す値をマッピングする手法です。その値は 0.0 から 1.0 の範囲を取り、グリフのエッジ上の画素は 0.5 になります。グリフ外の画素はその直近のエッジからの距離が増すにつれて 0.0 へと近づきます。グリフ内の画素は逆に直近のエッジからの距離が増すにつれて 1.0 へと近づきます。
[gallery include="5928, 5927, 5929" link="file"]
Distance Field は高解像度な画像やグリフのベクトルによる表現から生成することができます。最初のソリューションは典型的な総当たりによる手法であるため、非常に遅くなる可能性があります。そのため、このソリューションを我々が使う数十ものグリフ(中国語の場合は数百にもなります)を一度に生成するために適用するつもりにはなれませんでした。いくつかのツール(例えば、このツール)で与えられたフォントのグリフ群についてあらかじめ計算しておき、実行時にはその計算結果が保存されたファイルを使う方法もあります。しかし、この方法は開発者にとって不便であるため採用しませんでした。
その代わりに、距離データの生成にグリフのベクトルによる表現を用いることにしました。単純に、グリフのアウトラインを内側と外側に決められた距離だけ拡大し、その内外の枠とアウトラインとを距離のグラデーションで塗りつぶします。これらの結果を巨大なテクスチャの 64×64 画素のセルに 8-bit のチャンネルとして格納します。この方法によって(Kim に感謝します)、Distance Field を(平均的な)モバイルデバイスでグリフあたり 1msec 以下で生成できるようになり、実行時にどんなベクトルフォントも動的に利用できるようになります。
この技術ではテクスチャに対して GPU ネイティブの線形補間が利用できるという利点があります。エッジからの距離を正確に補間することができ、任意のサイズでグリフを再構成することができます。必要なのは Alpha Testing、すなわち、しきい値に応じて画素の表示の有無を決定することだけです。通常はグリフのエッジ上の値である 0.5 をそのしきい値として用います。この結果、グリフはどんなにズームしてもまるでベクトルグラフィックスであるかのように、くっきりとしたアウトラインを持ちます。唯一の欠点はとがった角が断ち切られることですが、この技術を使わずに拡大したグリフのひどい見た目を考えると取るに足りないものといえます。
[gallery link="file" include="5930, 5925"]
この技術は、全てをグラフィックハードウェアで実行時に「ただで」行えるため、パフォーマンスに影響を与えることなく素晴らしい見た目を得ることができます。
グリフの同じ Distance Field 表現を用いて、たった一行のシェーダーコードで高品質なアンチエイリアシングを実現できます。
varying highp vec2 sampleCoord;
uniform sampler2D texture;
uniform lowp vec4 color;
uniform highp float distMin;
uniform highp float distMax;
void main() {
gl_FragColor = color * smoothstep(distMin, distMax, texture2D(texture, sampleCoord).a);
}
Alpha Testing で一つのしきい値を使うのではなく、シェーダーでエッジをぼかすために二つの異なったしきい値を用います。入力された Distance Field の値は二つのしきい値の間で smoothstep で補間されてジャギーが取り除かれます。ぼかす領域の幅は二つのしきい値間の距離を変化させることで調整できます。グリフが小さくなれば、ぼかす領域はより広くなります。グリフが拡大されれば、ぼかす領域は狭くなります。
GPU が充分にパワフルであれば(すなわち、デスクトップマシンの GPU であれば)、シェーダーコードに数行追加することでサブピクセルアンチエイリアシングを行うこともできます。出力する画素のα値の計算に距離データを用いるのではなく、出力する画素で各色要素を別々に計算するために隣接画素のデータを用います。そのためには一つの画素ではなく、五画素をテクスチャから取り出す必要があります。赤要素は最も左から三つの Distance Field の値の平均に、緑要素は中間の三つの Distance Field の値の平均に、青要素は最も右から三つの Distance Field の値の平均になります。サブピクセルアンチエイリアシングは処理能力がより必要となるため、モバイルプラットフォームでは現状では無効になっています。もっとも、最近ではモバイルデバイスが高解像度のディスプレイを持つようになっており、サブピクセルアンチエイリアシングの利用が無意味になってきています。
Distance Field の利用によって、アンチエイリアシングに加えて、数行のシェーダーコードでアウトライン化や発光(glow)、影付けなどの特殊効果を行うこともできます。この技術を用いて QML の Text 要素に三つのスタイル(outline, raised, sunken)を実装することができました。例えばアウトライン化を行うには、二つの距離値の間にあるテクセルに異なった色を使用するだけです。これによって、これらの効果をより速く、よりきれいにスケーラブルに実行することができます(Scene Graph ベースではない QtQuick 1.0 では装飾されたテキストのスケーリングの品質はひどいものでした)。
与えられたフォントに対して、64×64 画素のサイズで一度のみラスタライズを行い、各グリフ(もしくはその Distance Field 表現)をキャッシュします。どんなサイズのグリフの描画においても、同じテクスチャが適切にスケーリングされて利用されます。QPainter で利用されていたネイティブのフォントレンダリングでは、アプリケーションで使われるサイズごとにグリフをキャッシュしています。(Distance Field では)あるフォントを様々なサイズで用いる際に、グラフィックメモリの使用量をより少なくすることができます。
グリフを任意のサイズにスケーリングできるようになったため、ズームしたときにグリフを正確な座標に配置するためにフォントのヒントを無効にする必要があります。その結果、グリフの座標がサブピクセル単位となります(すなわち、ピクセルグリッドに沿った配置ではありません)。
Qt 5 のリポジトリ からファイルを取得して、QtDeclarative モジュールにある qsgdistancefield から始まる名前のファイルを探してください。
この技術に関する詳細は Valve 社の論文 を参照してください。