世界を動かす技術を、日本語で。

GPUによるクリスピーなテキストのレンダリング

概要

リアルタイムテキストレンダリングの課題と進化を解説。 SDF方式の限界と新たなサブピクセルアンチエイリアス手法の導入。 フォント曲線データをGPUで動的にラスタライズするアプローチ。 高品質・低メモリ・高柔軟性を実現するパイプラインの概要。 最適化やカバレッジ計算の実装ポイントも紹介。

リアルタイムテキストレンダリングの再挑戦

  • 過去にも何度か テキストのリアルタイムレンダリングに取り組んできた経験
  • SDF(Signed Distance Field)方式 を以前採用したが、不満点が残存
  • 新しいOLEDモニター のサブピクセル構造によるフリンジ問題が再挑戦のきっかけ
  • GitHub上での議論参加 を通じてサブピクセルアンチエイリアスのアイデアを実装
  • 多様なフォント (丸み・鋭角・極細線など)で描画テストを実施

SDF方式の課題

  • 画質問題
    • 特定フォント(細い線・複雑なディテール)でアーティファクト発生
    • 解像度を上げても限界があり、細部が失われやすい
  • アトラスサイズの肥大化
    • SDFはオフライン生成でアトラス保存
    • 多言語フォント(日本語・中国語)は巨大なアトラスが必要
    • 複数フォント同時利用でメモリ・帯域コスト増大
  • 柔軟性の不足
    • ミニファイ・サブピクセルAAなど新機能追加が困難
    • 任意ベクター画像対応には焼き込みが必要で、ランタイム編集不可
  • システムの複雑化
    • 中間処理や外部ライブラリ依存で理解・保守が煩雑化
    • Bezier曲線 から直接描画できるシンプルな流れが理想

新手法の概要:ベクター曲線の動的ラスタライズ

  • テクスチャへの焼き込み不要
    • 表示中のグリフの曲線データを直接GPUへ送信
    • GPU上でリアルタイムにラスタライズ処理
  • アトラスには動的生成したグリフのみ保持
    • 使用中グリフはアトラスに残し、再利用・高品質化
    • サブピクセル単位での高精度アンチエイリアス実現
  • ベクター直描画により
    • 解像度制限なし
    • アトラスあたりのストレージ・メモリコスト大幅削減
    • サブピクセルAAや拡大縮小にも強い柔軟性

パイプライン全体の流れ

  • FreeType利用によるフォントデータの曲線抽出
    • フォント形式問わず対応
    • 各グリフのライン・2次Bezier(3点)・3次Bezier(4点)を抽出
  • 全ての曲線を2次Bezierへ変換
    • ラインは中点追加で2次Bezier化
    • 3次Bezierは2つの2次Bezierに分割(若干の誤差は許容)
    • TrueType(.ttf)は元々2次Bezierのみ
  • 曲線データのGPU転送・保存
    • 曲線の始点・終点(青)、制御点(赤)を明示的に管理

カバレッジ計算とアンチエイリアス

  • 各ピクセルごとに水平方向へレイを発射
    • 曲線との交点を検出し、ウィンディングナンバーを計算
    • 内外判定後、カバレッジ値に変換
  • サブピクセルごとに複数サンプルを蓄積
    • 数百サンプル(例:512)を平均化し高精度AA
    • ごく稀な誤判定も平均化で実用上無視可能
  • $R_2$系列による準ランダムサンプリング
    • サンプル分布の最適化で画質向上
  • 実装例コード
    • 曲線集合のビットセット管理
    • 各サブピクセルごとにカバレッジを加算・蓄積

曲線アクセスの最適化

  • グリフを水平バンドに分割し、曲線インデックスを管理
    • 各バンドに関連する曲線のみをビットで記録
    • 水平トレース時の曲線アクセス数を大幅削減
    • 高速化とメモリ効率化を両立

まとめ

  • SDF方式の限界を克服し、ベクター曲線の動的ラスタライズで高品質・低コストを実現
  • サブピクセルアンチエイリアスや多様なフォント対応が容易
  • シンプルで理解しやすいパイプライン設計
  • 今後もさらなる最適化・高精度化の余地あり

Hackerたちの意見

サブピクセルフォントレンダリングは可読性にとって重要だけど、著者が指摘しているように、既存のディスプレイ規格からピクセルレイアウトの仕様が得られないのは悲劇だよね。

なんでこんなことが何十年も続いてるのか理解できない :( 記事は素晴らしくて、「サブピクセル動物園」へのリンクもあって、いろんなバリエーションを紹介してるよ: https://geometrian.com/resources/subpixelzoo/

標準解像度のディスプレイだけだね。それに、そんなに「重要」ってわけでもなくて、ただの「あったらいいな」って感じ。世界はますますRetinaタイプのディスプレイに移行しているし、そこでサブピクセルレンダリングが必要な理由はほとんどないよ。それに、スクリーンショットが一つのサブピクセルレイアウトに縛られたり、ビットマップをスケールできなかったりと、いろいろ面倒なことが多い。CRTとRetinaの間のLCD時代の一時的な革新だったけど、今となっては過去を振り返るものだね。Appleが何年も前にmacOSからそれを取り除いたのには良い理由があるよ。

サブピクセルレンダリングはほとんどの言語では必要ないよ。ビットマップフォントやヒンティングされたベクターフォントは、アンチエイリアスなしでも素晴らしい可読性を提供するからね。中国語や日本語のように、非常に複雑な詳細を持つ文字を使う言語の場合だけ、サブピクセルレンダリングが重要になるんだ。

DisplayID標準(EDIDの現代の後継)がこれを許可することを意図しているようだね、https://en.wikipedia.org/wiki/DisplayID#0x0C_Display_device_... によると。ディスプレイメーカーはこれを実装してないのかな?どちらにしても、最も一般的なディスプレイモデルについては、ハードウェア情報データベースに簡単に導出して保存できる情報だよ。

Slugライブラリは、そんなGPUグリフラスタライザーを実装した商業ミドルウェアだよ。[1]: https://sluglibrary.com/

GTK4はレンダリングをGPUに移行して、RGBサブピクセルレンダリングを諦めたんだって。聞いたところによると、このGPU中心の決定がRGBサブピクセルレンダリングを続けるのを実用的でなくしたらしい。記事ではそれが可能だと示してるから、GTKの理由は別のものだったのか、提案された解決策には欠点があったのか、スタックに統合できなかったのかもしれないね。

Cosmic Text (Cosmic DE)は、swashを通じてGPU上でこれを実現するかもしれないよ。サブピクセルレンダリングがあるんだ。

すごい仕事だね。これに詳しくない人のために言うと、Valveは自社のゲームのためにSDFテキストレンダリングを発明したんだ。2007年にそのテーマに関する画期的な論文を発表したよ。今でもビデオゲームでは非常に人気のある技術で、ほとんど変わってない。2012年にはBehdad Esfahbodが、OpenGL ESを使ってGPU上で動作するSDFの実装であるGlyphyを作ったんだ。パフォーマンスが素晴らしく、テキストを素早く変形させる新しい機能を可能にしたことで広く称賛されているけど、あまり使われてはいない。現代のオペレーティングシステムやウェブブラウザは、1990年代スタイルのトゥルタイプラスタライゼーションに頼っていて、これらの技術は使われていないんだ。これは軽量で効果的なアプローチだけど、多くの機能が欠けている。記事でも示されているように、サブピクセルアライメントや任意のサブピクセルレイアウトはできない。ズームするとパフォーマンスに大きなペナルティがかかるし、傾きや回転、3D変換のような複雑な変形はテキストレンダリングエンジンではできない。回転や変形されたテキストが必要なら、ビットマップを再サンプリングするしかなくて、それはひどい見た目になる。なぜ進展がないのか?もしかしたら、リスクに対して得られるものが少なすぎて、ただの手間がかかりすぎるからかも。GPUアクセラレーションのテキストレンダリングを使うために、現代のウェブブラウザエンジンを再構築するのを想像できる?それは大変な作業だよ。グリフをレンダリングするのは一つのことだけど、行の折り返しをどう扱うかは?CPUとGPUの間でたくさんの通信が必要になりそうで、それは遅いし、ソフトウェアとGPUの間での深い統合が必要で、難しいよね。

SDFは万能ではないよ。SDFは、特定のピクセルから文字のエッジまでの局所的な_D_istanceを_F_ieldとしてエンコードすることで機能するんだ。つまり、文字の内側か外側かを示す_S_ignビットを使った2Dデータの配列だね。各文字には自分専用のデータマップがあって、それがGPUに優しい形式の画像ファイルにまとめられるんだ(人間が見るための画像を表さない場合は「マップ」と呼ばれることが多い)。それに、SDFレンダリングシェーダーで使うために、各文字のサブ画像がどこにあるかを示す記述ファイルも付いてくる。この文字の定義は、フィールド値の線形補間に対して非常に堅牢で、比較的低解像度のマップでもほぼ完璧なズームが可能になるんだ。GPUはマップ内のピクセル値を補間するのが得意だからね。でも、最も重要なのは、これらのマップは、レンダリングしたいすべての文字について既存のフォントシステムから開発中に前処理しなきゃいけないってこと。すべての文字。あなたのフォントがサポートしている。高解像度でビットマップフォントをレンダリングするよりはデータ量はかなり少ないけど、フォントの輪郭定義自体よりはずっと多いんだ。世界中の潜在的なテキストをサポートしたいもの、たとえばOSやブラウザは、SDFをテキストレンダリングシステムとして使うことはできないよ。なぜなら、Unicode文字セット全体のSDFマップが必要になるから。それは消費するには大きすぎる。ゲームにはうまく機能するけどね。ゲームは(一般的に)ローカライズがあまり必要ないし、完全に任意のテキストを表示しなくても大丈夫だから。元のSDFはEmojiもサポートできないんだ。なぜなら、グリフのエッジまでの距離しかエンコードしていなくて、グリフの内部の色については何も含まれていないから。ただし、複数の色をサポートするためのアルゴリズムの強化(マルチチャンネルSDF)もあるけど、色の総数には限界があるよ。実際、A) ゲーム内テキストにSDFを利用していて、B) グローバルコミュニティが交流するチャットシステムがあるゲームをよく見てみると、ゲーム内テキストとチャットシステムでテキストレンダリングに違いがあるのがわかると思う。

スキューや回転、3D変換みたいな複雑な変換はできないんだ。俺のテキストドキュメントビューアは、テキストを左から右に直線で表示するだけで十分なんだ。右から左もほぼ同じくらい簡単だと思うけど。中国語はまだ上から下に表示したいのかな?

分かりやすい説明ありがとう!こういう簡潔な概要を読むのが大好きなんだ。

現代のウェブブラウザエンジンをGPUアクセラレーションのテキストレンダリングを使うように書き換えることを想像できる? […] グリフをレンダリングするのは一つのことだけど、行の折り返しをどう扱うの?なんでそんなこと言ってるのか分からないけど、テキストの形状やレイアウト(行の折り返しを含む)はレンダリングとはほぼ無関係だよ。

現代のウェブブラウザエンジンをGPUアクセラレーションのテキストレンダリングを使うように書き換えることを想像できる? https://github.com/servo/pathfinder はGPUコンピュートシェーダーを使ってこれを実現していて、ハードウェアの3Dレンダリングパイプライン(SDFアプローチ)にこのタスクを組み込むよりもはるかにパフォーマンスがいいよ。

コードのリンクが見つからないんだけど、どこかにあるの?

WebGLやWebGPUでSDFやMSDFを実装する方法に興味があったら、僕が書いたこのチュートリアルを見てみてね:https://infinitecanvas.cc/guide/lesson-015#msdf。

これ、すごくいいね。WGPU(RustのWebGPU実装)にちょっと興味があって、君のチュートリアルはそれに関する上級コースのように見えるね。宣伝はしてないけど。前にJavaScriptの例をRustに翻訳したことがあって、学ぶには理想的なんだ。コードをコピー&ペーストできないから、APIが近いからコードを移植しやすいし、WGPUのドキュメントを使うきっかけにもなるんだ。

わお、このサイトのフォーマットめっちゃ好き!もっと教えてくれない?GPU関連のチュートリアルを作るのが好きで、君のように構成したいんだ。既存のテンプレートなの?何かのコースの一部なの?

テキストエディタをゼロから作るとき、一番驚いたのはテキストレンダリングがどれだけ遅くてコストがかかるかってこと。

ここにいる著者だよ!この投稿がここに来るとは思わなかった!読んでくれてるみんな、興味深いチャットに参加してくれてありがとう<3

すごく面白かった!こんな「穴」に入れるなんて羨ましいな。ちなみに、最初の「メニューUI」から最後まで、頭の中でペルソナの音楽が流れてたよ^^(最後の言葉を読むのは驚きだった)

画面の任意の位置にグリフを配置したいかもしれないけど、ピクセルグリッドに合わせる必要はないよね。いや、全然いらない。これは恐ろしい考えだよ。同じ文字が印刷されるたびに違って見える可能性があるってことだろ?それはすごく目立つし、本当に最悪だよ。例えば、コードの連続した行で等号を揃えると、すぐに文字が違うかどうか分かる。今の時代、ピクセルが小さすぎて、なんでみんな良質なビットマップフォントを使わないのか理解できないよ。俺は使ってるけど、本当に満足してる。シャープすぎるくらいで、正しく表示されるのはディスプレイのガンマに依存しないからね(これはTFAが全く触れてない深刻な問題だ)。

投稿には実際のアニメーションの例があるんだよね。長いテキストでサブピクセル位置を使いたくないかもしれないけど、アニメーションの遷移や一般的なアニメーションが必要な時には絶対に必要だよ。