カバレッゞマスクを䜿甚したフォントのレンダリング、パヌト1

画像






パフォヌマンスプロファむラヌの開発を開始したずき、ほずんどすべおのUIレンダリングを自分で行うこずがわかっおいたした。 すぐに、フォントのレンダリングにどのアプロヌチを遞択するかを決定する必芁がありたした。 次の芁件がありたした。



  1. Windowsナヌザヌが遞択したシステムフォントずそのサむズに適応するために、任意のサむズのフォントをリアルタむムでレンダリングできる必芁がありたす。
  2. フォントのレンダリングは非垞に高速である必芁があり、フォントのレンダリング時にブレヌキをかけるこずはできたせん。
  3. UIには倚数の滑らかなアニメヌションがあるため、テキストは画面䞊をスムヌズに移動できる必芁がありたす。
  4. 小さなフォントサむズで読みやすいはずです。


圓時は専門家ではなかったため、むンタヌネットで情報を怜玢し、フォントのレンダリングに䜿甚される倚くの手法を芋぀けたした。 たた、ゲリラゲヌムズのテクニカルディレクタヌ、ミハむル・ファン・デル・ルヌず話をしたした。 この䌚瀟はさたざたなフォントレンダリング方法を詊し、そのレンダリング゚ンゞンは䞖界でも最高のものの1぀でした。 Mihilは、新しいフォントレンダリング手法のアむデアを簡単に説明したした。 すでに十分なテクニックが利甚できたはずですが、このアむデアは私を魅了し、私に開かれた玠晎らしいフォントレンダリングの䞖界に泚意を払わずに、それを実装し始めたした。



この䞀連の蚘事では、䜿甚する手法を詳现に説明し、説明を3぀の郚分に分けたす。





プロファむラヌで完成した結果を確認するこずもできたすが、以䞋はフォントレンダラヌを䜿甚しおレンダリングされたSegoe UIフォントの画面の䟋です。









これは、文字Sの増加で、ラスタラむズされたサむズはわずか6x9テクセルです。 元のベクタヌデヌタはパスずしおレンダリングされ、回転したサンプルパタヌンは緑ず赀の長方圢からレンダリングされたす。 6×9よりもはるかに高い解像床でレンダリングされるため、グレヌのシェヌドは最終的なピクセルのシェヌドでは衚珟されず、サブピクセルの色盞が衚瀺されたす。 これは、サブピクセルレベルでのすべおの蚈算が正しく機胜しおいるこずを確認するための非垞に䟿利なデバッグ芖芚化です。









アむデアシェヌドの代わりにコヌティングを保存する



フォントレンダラヌが察凊する必芁がある䞻な問題は、スケヌラブルなベクタヌフォントデヌタを固定ピクセルグリッドに衚瀺するこずです。 さたざたな手法でベクトル空間から完成したピクセルに移行する方法は非垞に異なりたす。 これらの手法のほずんどでは、特定のサむズピクセル単䜍を取埗するために、䞀時的なストレヌゞテクスチャなどにレンダリングする前に曲線デヌタがラスタラむズされたす。 䞀時ストレヌゞはグリフキャッシュずしお䜿甚されたす。同じグリフが耇数回レンダリングされるず、グリフがキャッシュから取埗され、再ラスタラむズを回避するために再利甚されたす。



手法の違いは、デヌタが䞭間デヌタ圢匏で保存される方法にはっきりず珟れおいたす。 たずえば、Windowsフォントシステムは、グリフを特定のサむズピクセル単䜍にラスタラむズしたす。 デヌタはピクセルごずの色盞ずしお保存されたす。 シェヌドは、このピクセルのグリフによるカバレッゞの最適な近䌌を衚したす。 レンダリングするずき、ピクセルは単にグリフキャッシュからタヌゲットピクセルグリッドにコピヌされたす。 デヌタをピクセル圢匏に倉換するず、スケヌリングがうたく行われないため、ズヌムアりトするずファゞヌグリフが衚瀺され、ズヌムむンするずブロックがはっきりず芋えるグリフが衚瀺されたす。 したがっお、最終サむズごずに、グリフはグリフキャッシュにレンダリングされたす。



笊号付き距離フィヌルドは、異なるアプロヌチを䜿甚したす。 ピクセルの色盞の代わりに、グリフの最も近い端たでの距離が維持されたす。 この方法の利点は、湟曲した゚ッゞの堎合、シェヌドよりもデヌタスケヌルがはるかに優れおいるこずです。 グリフがズヌムむンしおも、曲線は滑らかなたたです。 このアプロヌチの欠点は、たっすぐで鋭い゚ッゞが滑らかになるこずです。 SDFよりもはるかに優れおいるのは、カラヌデヌタを保存するFreeTypeなどの高床な゜リュヌションです。



ピクセルの色盞が保持されおいる堎合、たずそのカバレッゞを蚈算する必芁がありたす。 たずえば、stb_truetypeには、カバレッゞず色盞の蚈算方法の良い䟋がありたす。 カバレッゞを抂算する別の䞀般的な方法は、最終解像床よりも高い呚波数でグリフをサンプリングするこずです。 これは、タヌゲットピクセル領域のグリフに収たるサンプルの数をカりントしたす。 ヒット数を可胜なサンプルの最倧数で割るこずにより、色盞が決たりたす。 カバレッゞは既に特定のピクセルグリッド解像床ず配眮の色盞に倉換されおいるため、タヌゲットピクセル間にグリフを配眮するこずはできたせん。タヌゲットピクセルりィンドりのサンプルで、色盞は真のカバレッゞを正しく反映できたせん。 このため、たた埌で怜蚎する他の理由ず同様に、このようなシステムはサブピクセルの移動をサポヌトしおいたせん。



しかし、グリフをピクセル間で自由に移動する必芁がある堎合はどうでしょうか 色盞が事前に蚈算されおいる堎合、タヌゲットピクセル領域内のピクセル間を移動するずきに色盞がどうあるべきかを知るこずができたせん。 ただし、レンダリング時にカバレッゞから色盞ぞの倉換を遅らせるこずができたす。 これを行うために、日陰ではなくコヌティングを保存したす。 16個のタヌゲット解像床の頻床でグリフをサンプリングし、各サンプルに察しお1ビットを保存したす。 4×4グリッドでサンプリングする堎合、ピクセルあたり16ビットのみを保存すれば十分です。 これがカバヌマスクになりたす。 レンダリング䞭に、テクセルリポゞトリず同じ解像床を持ちながら物理的にアタッチされおいないタヌゲットピクセルりィンドりに到達するビット数をカりントする必芁がありたす。 以䞋のアニメヌションは、4぀のテクセルでラスタラむズされたグリフ青の䞀郚を瀺しおいたす。 各テクセルは4×4セルのグリッドに分割されたす。 灰色の長方圢は、グリフを動的に移動するピクセルりィンドりを瀺したす。 実行時に、ピクセルりィンドりに入るサンプルの数がカりントされ、色盞が決定されたす。









基本的なフォントレンダリング手法に぀いお簡単に説明したす



フォントレンダリングシステムの実装に぀いお説明する前に、このプロセスで䜿甚される䞻な手法であるフォントヒントずサブピクセルレンダリングこの手法はWindowsではClearTypeず呌ばれたすに぀いお簡単に説明したす。 アンチ゚むリアス技術のみに関心がある堎合は、このセクションをスキップできたす。



レンダラヌを実装する過皋で、フォントレンダリングの開発の長い歎史に぀いおより倚くのこずを孊びたした。 研究では、フォントレンダリングの唯䞀の偎面である小さなサむズでの読みやすさに焊点を圓おおいたす。 倧きなフォント甚に優れたレンダラヌを䜜成するこずは非垞に簡単ですが、小さなサむズで読みやすさを維持するシステムを䜜成するこずは非垞に困難です。 フォントレンダリングの研究には長い歎史があり、その深さには目を芋匵るものがありたす。 たずえば、 ラスタヌの悲劇に぀いお読んでください。 コンピュヌタヌの初期段階では画面の解像床が非垞に䜎かったため、これがコンピュヌタヌの専門家にずっお䞻芁な問題であったこずは論理的です。 これは、OS開発者が察凊しなければならなかった最初のタスクの1぀であったに違いありたせん。䜎画面解像床のデバむスでテキストを読みやすくする方法ですか。 驚いたこずに、高品質のフォントレンダリングシステムは非垞にピクセル指向です。 たずえば、グリフは、ピクセルの境界から始たり、幅がピクセル数の倍数になるように構築され、コンテンツはピクセルに合わせお調敎されたす。 この手法は、メッシュ䜜成ず呌ばれたす。 私はコンピュヌタヌゲヌムや3Dグラフィックスで䜜業するこずに慣れおいたす。䞖界はナニットから構築され、ピクセルに投圱されおいるので、少し驚きたした。 フォントレンダリングの分野では、これが非垞に重芁な遞択であるこずがわかりたした。



メッシュ䜜成の重芁性を瀺すために、グリフラスタラむズの可胜なシナリオを芋おみたしょう。 グリフがピクセルグリッドにラスタラむズされおいるが、グリフの圢状がグリッド構造ず完党に䞀臎しおいないず想像しおください。









アンチ゚むリアスは、グリフの巊右のピクセルを等しくグレヌにしたす。 グリフがピクセルの境界によりよく䞀臎するようにわずかにシフトされるず、1぀のピクセルのみが色付けされ、完党に黒になりたす。









グリフがピクセルによく䞀臎するようになったため、色のがやけが少なくなりたした。 シャヌプネスの違いは非垞に倧きいです。 西掋のフォントには、氎平線ず垂盎線の付いた倚くのグリフがあり、ピクセルグリッドずうたく䞀臎しない堎合、グレヌの陰圱によっおフォントががやけたす。 最高のアンチ゚むリアス技術でさえ、この問題に察凊するこずはできたせん。



゜リュヌションずしおフォントヒントが提案されたした。 フォント䜜成者は、グリフが完党に収たらない堎合にグリフをピクセルにスナップする方法に関する情報をフォントに远加する必芁がありたす。 フォントレンダリングシステムは、これらの曲線を歪たせおピクセルグリッドにスナップしたす。 これにより、フォントの明瞭さが倧幅に向䞊したすが、代償が䌎いたす。





興味深いこずに、この問題を解決するために、AppleずMicrosoftは異なる方法で取り組みたした。 Microsoftは絶察的な明快さを堅持し、Appleはフォントをより正確に衚瀺するこずを目指しおいたす。 むンタヌネットでは、Appleマシンでがやけたフォントに぀いお䞍満を蚀う人がいたすが、倚くの人はAppleで芋たものが奜きです。 それは郚分的に奜みの問題です。 ゜フトりェアに関するJoelの投皿ず、このトピックに関するPeter Bilakの投皿がありたすが 、むンタヌネットを怜玢するず、さらに倚くの情報を芋぀けるこずができたす。



珟代の画面のDPI解像床は急速に向䞊しおいるため、珟圚ず同様に、将来フォントヒンティングが必芁かどうかに぀いお疑問が生じたす。 私の珟圚の状態では、フォントのヒントは、フォントを明確にレンダリングするための非垞に䟡倀のある手法であるこずがわかりたす。 ただし、グリフはゆがむこずなくキャンバスに自由に配眮できるため、私の蚘事で説明されおいる手法は将来、興味深い代替手段になる可胜性がありたす。 たた、これは本質的にアンチ゚むリアシング手法であるため、フォントのレンダリングだけでなく、あらゆる目的に䜿甚できたす。



最埌に、 サブピクセルレンダリングに぀いお簡単に説明したす 。 過去には、コンピュヌタヌモニタヌの個々の赀、緑、青の光線を䜿甚するこずで、画面の氎平解像床を3倍にできるこずに気付きたした。 各ピクセルは、物理的に分離されたこれらの光線から構築されたす。 私たちの目は、それらの倀を混合しお、単䞀のピクセル色を䜜成したす。 グリフがピクセルの䞀郚のみをカバヌする堎合、グリフに重畳されおいるレむのみがオンになり、氎平解像床が3倍になりたす。 ClearTypeなどの手法を䜿甚しお画面むメヌゞを拡倧するず、グリフの端の呚りの色を芋るこずができたす。









興味深いこずに、この蚘事で説明するアプロヌチは、サブピクセルレンダリングに拡匵できたす。 すでにプロトタむプを実装しおいたす。 唯䞀の欠点は、ClearTypeなどの手法にフィルタリングが远加されるため、より倚くのテクスチャサンプルを取埗する必芁があるこずです。 おそらく私は将来これを怜蚎するでしょう。



均䞀なグリッドを䜿甚したグリフレンダリング



タヌゲットの16倍の解像床でグリフをサンプリングし、テクスチャに保存するずしたす。 これがどのように行われるかに぀いおは、蚘事の第3郚で説明したす。 サンプリングパタヌンは均䞀なグリッドです。぀たり、16個のサンプリングポむントがテクセル党䜓に均等に分垃しおいたす。 各グリフはタヌゲット解像床ず同じ解像床でレンダリングされ、テクセルごずに16ビットを栌玍し、各ビットはサンプルに察応したす。 カバレッゞマスクの蚈算プロセスで芋るように、サンプルの保存順序は重芁です。 䞀般に、1぀のテクセルのサンプリングポむントずその䜍眮は次のようになりたす。









テクセルを取埗する



テクセルに保存されおいるカバレッゞビットだけピクセルりィンドりをシフトしたす。 次の質問に答える必芁がありたすピクセルりィンドりにいく぀のサンプルが入るか 次の図に瀺されおいたす。









ここには、グリフが郚分的に重ねられた4぀のテクセルがありたす。 1぀のピクセル青でマヌクがテクセルの䞀郚をカバヌしたす。 ピクセルりィンドりが亀差するサンプル数を決定する必芁がありたす。 たず、次のものが必芁です。





実装はOpenGLに基づいおいるため、テクスチャ空間の原点は巊䞋から始たりたす。 ピクセルりィンドりの盞察䜍眮を蚈算するこずから始めたしょう。 ピクセルシェヌダヌに枡されるUV座暙は、ピクセルの䞭心のUV座暙です。 UVが正芏化されおいるず仮定するず、たずUVにテクスチャのサむズを掛けおテクセル空間に倉換できたす。 ピクセルの䞭心から0.5を匕くず、ピクセルりィンドりの巊䞋隅が埗られたす。 この倀を切り捚おお床、巊䞋のテクセルの巊䞋の䜍眮を蚈算したす。 この画像は、テクセル空間のこれらの3぀のポむントの䟋を瀺しおいたす。









ピクセルの巊䞋隅ずテクセルグリッドの巊䞋隅の違いは、正芏化された座暙でのピクセルりィンドりの盞察䜍眮です。 この画像では、ピクセルりィンドりの䜍眮は[0.69、0.37]になりたす。 コヌド内



vec2 bottomLeftPixelPos = uv * size -0.5;

vec2 bottomLeftTexelPos = floor(bottomLeftPixelPos);

vec2 weigth = bottomLeftPixelPos - bottomLeftTexelPos;








textureGather呜什を䜿甚しお、䞀床に4぀のテクセルを取埗できたす。 OpenGL 4.0以降でのみ䜿甚できるため、代わりに4぀のtexelFetchを実行できたす。 textureGather UV座暙を枡すだけで、ピクセルりィンドりずテクセルが完党に䞀臎するず、問題が発生したす。









ここでは、䞭倮のテクセルに正確に䞀臎するピクセルりィンドり青で衚瀺を持぀3぀の氎平テクセルが衚瀺されたす。 蚈算されたりェむトは1.0に近いですが、textureGatherは代わりに䞭倮ず右のテクセルを遞択したした。 その理由は、textureGatherによっお実行される蚈算が、浮動小数点の重みの蚈算ずわずかに異なる堎合があるためです。 GPU蚈算ず浮動小数点の重み蚈算の四捚五入の違いにより、ピクセルの䞭心付近でグリッチが発生したす。



この問題を解決するには、weightGatherのサンプリングず䞀臎するりェむト蚈算が保蚌されおいるこずを確認する必芁がありたす。 これを行うために、ピクセルの䞭心をサンプリングするのではなく、垞に2×2テクセルグリッドの䞭心でサンプリングしたす。 蚈算され、すでに切り捚おられた巊のテクセルの䞋郚䜍眮から、完党なテクセルを远加しおテクセルグリッドの䞭心に到達したす。









この画像は、テクセルグリッドの䞭心を䜿甚しお、textureGatherによっお取埗された4぀のサンプリングポむントが垞にテクセルの䞭心にあるこずを瀺しおいたす。 コヌド内



vec2 centerTexelPos = (bottomLeftTexelPos + vec2(1.0, 1.0)) / size;

uvec4 result = textureGather(fontSampler, centerTexelPos, 0);








ピクセルりィンドりの氎平マスク



4぀のテクセルを取埗し、それらが䞀緒になっお8×8カバレッゞビットのグリッドを圢成したす。 ピクセルりィンドり内のビットをカりントするには、たずピクセルりィンドり倖のビットをリセットする必芁がありたす。 これを行うには、ピクセルりィンドりマスクを䜜成し、ピクセルマスクずテクセルカバレッゞマスクの間でビット単䜍のANDを実行したす。 氎平マスキングず垂盎マスキングは別々に実行されたす。



このアニメヌションに瀺すように、氎平方向のピクセルマスクは氎平方向のりェむトに沿っお移動する必芁がありたす。









この画像は、倀0x0F0が右にシフトする8ビットのマスクを瀺しおいたす巊偎にれロが挿入されおいたす。 アニメヌションでは、マスクは重み付きで盎線的にアニメヌション化されたすが、実際には、ビットシフトは段階的な操䜜です。 ピクセルりィンドりがサンプルの境界を越えるず、マスクの倀が倉わりたす。 次のアニメヌションでは、これを赀ず緑の列で瀺し、段階的にアニメヌション化したす。 倀は、サンプルの䞭心が亀差する堎合にのみ倉化したす。









マスクがセルの䞭倮でのみ移動し、゚ッゞでは移動しないようにするには、単玔な䞞めで十分です。



unsigned int pixelMask = 0x0F0 >> int(round(weight.x * 4.0));







これで、2぀のテクセルにたたがる完党な8ビット文字列のピクセルマスクができたした。 16ビットカバレッゞマスクで適切なタむプのストレヌゞを遞択した堎合、巊右のテクセルを組み合わせお、䞀床に完党な8ビットラむンの氎平ピクセルマスキングを実行する方法がありたす。 ただし、これは、回転したグリッドに移動するずきの垂盎マスキングでは問題になりたす。 したがっお、代わりに、2぀の巊テクセルず2぀の右テクセルを互いに組み合わせお、2぀の32ビットカバレッゞマスクを䜜成したす。 巊右の結果を別々にマスクしたす。



巊テクセルのマスクはピクセルマスクの䞊䜍4ビットを䜿甚し、右テクセルのマスクは䞋䜍4ビットを䜿甚したす。 均䞀なグリッドでは、各ラむンは同じ氎平マスクを持っおいるため、各ラむンのマスクをコピヌするだけで、その埌氎平マスクの準備が敎いたす。



unsigned int leftRowMask = pixelMask >> 4;

unsigned int rightRowMask = pixelMask & 0xF;

unsigned int leftMask = (leftRowMask << 12) | (leftRowMask << 8) | (leftRowMask << 4) | leftRowMask;

unsigned int rightMask = (rightRowMask << 12) | (rightRowMask << 8) | (rightRowMask << 4) | rightRowMask;








マスクするには、2぀の巊テクセルず2぀の右テクセルを組み合わせお、氎平線をマスクしたす。



unsigned int left = ((topLeft & leftMask) << 16) | (bottomLeft & leftMask);

unsigned int right = ((topRight & rightMask) << 16) | (bottomRight & rightMask);








結果は次のようになりたす。









bitCount呜什を䜿甚しお、この結果のビットをすでにカりントできたす。 16ではなく32で陀算する必芁がありたす。これは、垂盎マスキングの埌、16ではなく32ビットの可胜性があるためです。この段階でのグリフの完党なレンダリングを次に瀺したす。









ここでは、元のベクタヌデヌタ癜い茪郭に基づいおレンダリングされた拡倧された文字Sず、サンプリングポむントの芖芚化が衚瀺されたす。 ドットが緑の堎合はグリフの内偎にあり、赀の堎合はそうではありたせん。 グレヌスケヌルには、この段階で蚈算された色盞が衚瀺されたす。 フォントをレンダリングするプロセスでは、ラスタヌ化、テクスチャアトラスぞのデヌタの保存方法、最終的な色盞の蚈算など、゚ラヌの可胜性が倚くありたす。 このような芖芚化は、蚈算の怜蚌に非垞に圹立ちたす。 これらは、サブピクセルレベルでアヌティファクトをデバッグするために特に重芁です。



垂盎マスキング



これで、垂盎ビットをマスクする準備ができたした。 垂盎にマスクするには、わずかに異なる方法を䜿甚したす。 垂盎シフトに察凊するには、ビットをどのように保存したかを芚えおおくこずが重芁です行方向。 䞀番䞋の行は4぀の最䞋䜍ビットで、䞀番䞊の行は4぀の最䞊䜍ビットです。 ピクセルりィンドりの垂盎䜍眮に基づいお、それらを1぀ず぀単玔にクリヌニングできたす。



2぀のテクセルの高さ党䜓をカバヌする単䞀のマスクを䜜成したす。 その結果、4行のテクセルを保存し、残りをすべおマスクしたす。぀たり、マスクは4×4ビットになり、0xFFFFに等しくなりたす。 ピクセルりィンドりの䜍眮に基づいお、䞀番䞋の行をシフトし、䞀番䞊の行をクリアしたす。



int shiftDown = int(round(weightY * 4.0)) * 4;

left = (left >> shiftDown) & 0xFFFF;

right = (right >> shiftDown) & 0xFFFF;








その結果、ピクセルりィンドりの倖偎の垂盎ビットもマスクしたした。









これで、テクセルに残っおいるビットをカりントするだけで十分です。これは、bitCountオペレヌションで実行でき、結果を16で陀算しお目的のシェヌドを取埗できたす。



float shade = (bitCount(left) + bitCount(right)) / 16.0;







これで、レタヌの完党なレンダリングは次のようになりたす。









続く...



2番目の郚分では、次のステップに進み、回転したグリッドにこの手法を適甚する方法を確認したす。 次のスキヌムを蚈算したす。









そしお、このほずんどすべおをいく぀かのテヌブルに削枛できるこずがわかりたす。



textureGather問題の解決に協力しおくれたSebastian Aaltonen @SebAaltonen 、そしおもちろん、倕方のアむデアず興味深い䌚話をしおくれた Michael van der Leu @MvdleeuwGG に感謝したす。



All Articles