ラスタライズの原理(三角形の画素化)
三角形をどの画素で塗るか、色や UV をどう補間するかを、エッジ関数と重心座標という一つの仕組みで説明します。トップレフト規則や遠近補正まで、GPU が毎フレーム行う計算の中身が腑に落ちます。
- 1.ラスタライズは三角形を画素の集合へ変換する処理で、各画素中心が三角形の内側かをエッジ関数(辺に対する符号付き面積)の符号で判定する。
- 2.3辺のエッジ関数値を正規化すると重心座標になり、頂点の色や深度・テクスチャ座標をその重みで線形補間できる。隙間や重なりはトップレフト規則で排他的に埋める。
- 3.スクリーン空間の線形補間は透視投影で歪むため、属性を w で割って補間し 1/w で割り戻す遠近補正補間が必須になる。
ラスタライズは「連続な三角形」を「離散な画素」へ翻訳する
3D レンダリングの主流パイプラインでは、あらゆる形状は最終的に三角形の集まりに分解され、頂点シェーダを経てスクリーン座標へ投影されます。ここで残る問いはただ一つ、「この三角形は、どの画素を、どんな色で塗るのか」 です。この連続的な図形を離散的な画素グリッドへ写し取る処理がラスタライズ(走査変換, rasterization)であり、GPU が毎フレーム何百万回も実行する中核処理です。
ラスタライズが答えるべきことは2つに分けられます。第一に 被覆判定(coverage)——各画素が三角形の内側にあるか。第二に 属性補間(interpolation)——内側なら、3頂点が持つ色・深度・テクスチャ座標をその画素位置に応じてどう混ぜるか。現代の GPU はこの両方を、後述する エッジ関数 と 重心座標 という一組の数学で統一的に解きます。素朴なアルゴリズムから入り、なぜ現在の形に落ち着いたかを追います。
走査変換の素朴な方法とその限界
古典的な走査変換は、三角形を水平な走査線(スキャンライン)で輪切りにし、各行で三角形の左右の辺と交わる x 座標を求め、その区間を塗るという手順でした。辺の傾きから x を1画素ずつ加算していけば、行ごとの塗り開始・終了位置が増分計算だけで求まります。
この方式は逐次計算としては効率的ですが、弱点があります。走査線が上から下へ順に依存するため 並列化しにくい、辺のクリッピングや水平辺の扱いで場合分けが増える、そして属性補間を辺に沿った補間と行内の補間の二段構えで持たなければならない、といった点です。数千コアが同時に画素を処理する GPU では、行の順序に縛られず 各画素を独立に判定できる 方式のほうが圧倒的に都合が良い。そこで主流になったのがエッジ関数によるアプローチです。
エッジ関数 ── 辺に対する符号付き面積
三角形の1つの辺を、始点 A から終点 B への有向線分と見ます。ある点 P がこの辺の「どちら側」にあるかは、ベクトル AB と AP の外積(2D では z 成分のスカラー)の符号で判定できます。これを エッジ関数 と呼びます。
辺 A→B に対するエッジ関数(点 P を評価):
E_AB(P) = (Bx − Ax)·(Py − Ay) − (By − Ay)·(Px − Ax)
符号の意味(頂点を時計回り CW に並べた場合):
E_AB(P) > 0 : P は辺 A→B の内側(右側)
E_AB(P) = 0 : P は辺 A→B の直線上
E_AB(P) < 0 : P は辺 A→B の外側(左側)
幾何的意味: E_AB(P) は三角形 A,B,P の符号付き面積の2倍。
エッジ関数の要は 1次式である ことです。P の座標について線形なので、隣の画素へ1つ動いたときの値の変化は一定です。したがって、ある基準点で一度だけ値を計算すれば、あとは x 方向・y 方向それぞれに固定の増分(辺の係数そのもの)を足し引きするだけで、全画素の値を掛け算なしに更新できます。これがハードウェア実装で決定的に効く性質です。
三角形の3辺すべてについてエッジ関数を用意し、3つの符号がそろって内側を指すか で被覆を判定します。頂点の並び順(ワインディング)が時計回りか反時計回りかで内側の符号が反転するため、実装では三角形全体の符号付き面積の符号を基準にそろえます。
実際の GPU は三角形を囲む最小の矩形(バウンディングボックス)を求め、その中の画素だけを対象にします。矩形内の各画素で3つのエッジ関数を並列に評価し、全て内側なら塗る。行順の依存がないため画素ごと・タイルごとに完全並列化でき、これが走査線方式に対する本質的な優位点です。多くの実装は 2x2 や 8x8 のタイル単位でまとめて評価し、キャッシュ効率と早期棄却を稼ぎます。
重心座標 ── 被覆判定と属性補間を橋渡しする
エッジ関数の値は、そのまま属性補間の重みに転用できます。三角形の頂点を V0, V1, V2 とし、各頂点の 対辺 に対するエッジ関数値を三角形全体の面積で正規化すると、重心座標(barycentric coordinates)w0, w1, w2 が得られます。
重心座標(面積比として定義):
Area = E_V0V1V2 全体の符号付き面積(の2倍)
w0 = (P,V1,V2 の面積) / Area ← V0 の対辺 V1→V2 のエッジ関数
w1 = (V0,P,V2 の面積) / Area ← V1 の対辺 V2→V0 のエッジ関数
w2 = (V0,V1,P の面積) / Area ← V2 の対辺 V0→V1 のエッジ関数
性質:
w0 + w1 + w2 = 1 (常に成立)
全て 0 以上 ⇔ P は三角形の内側(境界含む)
P = w0·V0 + w1·V1 + w2·V2 (位置そのものを再現)
重心座標が優れているのは、被覆判定に使った量がそのまま補間の重みになる 点です。wk が全て 0 以上なら内側、という判定は3つのエッジ関数の符号判定と同じものです。そして頂点が持つ任意の属性 attr(色、法線、テクスチャ座標 U/V、深度 Z など)は、次の1本の式で画素位置での値に補間できます。
スクリーン空間の線形補間(アフィン補間):
attr(P) = w0·attr0 + w1·attr1 + w2·attr2
深度バッファ(Z バッファ)に書き込む深度値も、この重心補間で求めます(深度については後述の遠近補正で扱う NDC の Z を線形補間するのが標準です)。頂点シェーダの出力を画素シェーダの入力へ渡す「varying 変数の補間」も、原理はこの重み付き和です。
トップレフト規則 ── 辺を共有する三角形の隙間と二重描画を防ぐ
隣り合う2つの三角形が1つの辺を共有するとき、その辺上に中心が乗った画素をどう扱うかが問題になります。両方の三角形で描けば 同じ画素を2回塗る(半透明合成で色が濃くなる、ステンシル計算が狂う)。どちらも描かなければ 1画素幅の隙間 ができる。エッジ関数値がちょうど 0 になる境界上の画素を、隣接する三角形間で 排他的に、かつ漏れなく どちらか一方へ割り当てる規約が必要です。
これを解決するのが トップレフト規則(top-left rule, フィルルール)です。エッジ関数値が 0(=画素中心が辺上)のとき、その辺が三角形にとって「トップ辺」または「レフト辺」であれば内側とみなし、それ以外の辺(ボトム辺・ライト辺)上なら外側とみなします。
| 辺の種類 | 定義 | 境界上(E=0)の画素の扱い |
|---|---|---|
| トップ辺 | 水平で、他の2辺より上にある辺(三角形が辺の下側にある) | 内側に含める(描く) |
| レフト辺 | 水平でなく、三角形の左側にある辺(1つか2つ存在しうる) | 内側に含める(描く) |
| ボトム辺 / ライト辺 | 上記以外の辺(水平で下にある/右側にある) | 外側とする(描かない) |
共有辺は一方の三角形にとってレフト辺なら、他方にとっては必ずライト辺になります。トップ辺とボトム辺も同様に裏表の関係です。したがって境界画素はどちらか片方だけに属し、隙間も二重描画も生じません。実装上は、判定式を「E > 0、または E = 0 かつその辺がトップ/レフト」という形に整え、E > 0 を E > 0 || (E == 0 && isTopLeft) として被覆に含めます。Direct3D や OpenGL のラスタライズ規則も、この塗りつぶし規約を仕様として定めています。
トップレフト規則は「見た目の細い隙間」対策と思われがちですが、実害はむしろ加算合成・アルファブレンド・ステンシル増減にあります。共有辺の画素を二重にカバーすると、半透明ポリゴンの継ぎ目が濃く見えたり、ステンシルシャドウのカウントが1ずれて影が欠けたりします。境界を排他化する規約は、正しさのための必須要件です。
遠近補正補間 ── スクリーン上の線形補間はなぜ歪むのか
ここまでの線形補間(アフィン補間)には落とし穴があります。透視投影を経た後のスクリーン空間で属性を単純に線形補間すると、テクスチャや色が歪む のです。奥へ傾いた床にタイル模様を貼ると、遠い側のタイルが不自然に間延びして見える——これがアフィンテクスチャマッピングの破綻で、初期の 3D ゲーム機で顕著でした。
原因は透視除算にあります。カメラ空間からクリップ空間へ移すと各頂点は同次座標 w を持ち、スクリーン座標は x/w, y/w で得られます。この除算は非線形なので、スクリーン空間で等間隔な画素は、元の3Dサーフェス上(あるいはテクスチャ空間)では等間隔ではありません。奥にある部分ほどスクリーン上で圧縮されるため、スクリーン座標に対して線形な補間はサーフェス上では線形でなくなります。
正しくは、属性そのものではなく「属性を w で割った値」を線形補間し、同時に補間した 1/w で最後に割り戻す 必要があります。これが遠近補正補間(perspective-correct interpolation)です。
遠近補正補間(属性 attr の場合):
1. 各頂点で attr / w と 1 / w を用意する
2. スクリーン空間の重心座標 w0,w1,w2 で線形補間:
num = w0·(attr0/w0w) + w1·(attr1/w1w) + w2·(attr2/w2w)
den = w0·(1/w0w) + w1·(1/w1w) + w2·(1/w2w)
3. 割り戻す:
attr(P) = num / den
※ w0w,w1w,w2w は各頂点のクリップ空間 w(重心の重み w0..w2 とは別物)
要するに、線形なのは 1/w 空間 だという事実がすべてです。奥行きに反比例する 1/w はスクリーン座標に対して線形に変化するので、これを軸に補間し、最後に射影を打ち消すことで、サーフェス上で正しい(=遠近が効いた)補間値が復元されます。GPU の varying 補間は既定でこの遠近補正を行い、色・法線・UV などに適用します。
遠近補正が必要なのは UV や色などサーフェス属性です。一方 Z バッファ用の深度は、NDC(正規化デバイス座標)の Z をスクリーン空間で線形補間する方式が標準で、これは NDC の Z が 1/w に対して線形(1 次関数)に変化する(=スクリーン空間で線形補間できる)ため深度比較や Early-Z と整合します。「サーフェス属性は遠近補正、深度の比較用値は NDC の Z を線形補間」と役割で覚えると混乱しません。なお 1/w が線形という同じ性質が、深度精度が近距離に偏る(遠方で粗くなる)理由でもあります。
まとめ
- ラスタライズは連続な三角形を離散な画素へ変換する処理で、被覆判定 と 属性補間 の2つに分解できる。走査線方式に代わり、画素ごとに独立評価できるエッジ関数方式が GPU の主流。
- エッジ関数 は辺に対する符号付き面積を表す1次式で、3辺の符号がそろえば内側。線形性ゆえに増分計算だけで全画素を並列評価でき、バウンディングボックス内で処理する。
- エッジ関数値を正規化した 重心座標
w0,w1,w2(和が 1)が、被覆判定と属性の線形補間を同一の仕組みで担う。深度や varying の補間もこの重み付き和。 - トップレフト規則 は共有辺上の画素を隣接三角形間で排他的に割り当て、隙間と二重描画を防ぐ。合成・ステンシルの正しさに必須。
- 透視投影後のスクリーン空間では線形補間が歪むため、属性を w で割って補間し 1/w で割り戻す遠近補正補間 が必要。線形なのは 1/w 空間だという事実が核心で、深度精度の偏りも同じ性質に由来する。
グラフィックス Article
ラスタライズの原理(三角形の画素化)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ラスタライズ
比較で見る軸
難易度: advanced / カテゴリ: グラフィックス / タグ数: 5
導入後に効く点
3辺のエッジ関数値を正規化すると重心座標になり、頂点の色や深度・テクスチャ座標をその重みで線形補間できる。隙間や重なりはトップレフト規則で排他的に埋める。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- グラフィックス
- タグ数
- 5
判断チェックリスト
- 自社の用途が「ラスタライズ / GPU」に近いか確認する。
- 強みである「ラスタライズは三角形を画素の集合へ変換する処理で、各画素中心が三角形の内側かをエッジ関数(辺に対する符号付き面積)の符号で判定する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。