順序非依存透過(OIT)
半透明が回転で前後関係を崩す悩みを、なぜソートでは解けないかという根本から解決できます。加重ブレンドOITとピクセルリンクリスト方式を、正確さと負荷の得失で選び分けられるようになります。
- 1.アルファブレンドは非可換な演算なので合成結果が描画順に依存し、正しくは奥→手前のソートが要る。だがメッシュ内の自己交差や相互貫入では三角形単位のソートでも順序が一意に定まらず破綻する。
- 2.加重ブレンドOIT(WBOIT)はソートを捨て、色×αと不透明度の総和を深度依存の重みで累積して1回で合成する近似。加算は可換なので順序非依存で軽いが、重なりが厚いと色が濁る。
- 3.A-buffer/ピクセルリンクリスト方式は各画素に全フラグメントを溜め、解決パスで深度ソートして厳密に合成する。正確だが層数が可変でメモリ帯域を食い、重畳の深いシーンで破綻しうる。
透過はなぜ「順序問題」になるのか
不透明物体は 深度バッファ が画素ごとに最前面だけを残すため、どんな順で描いても結果は同じです。ところが半透明(アルファブレンド)はそうはいきません。ブレンドは「すでに描かれている背景色」と「今の色」をアルファで混ぜる演算で、混合結果が描画順に依存 します。
標準的な over 演算子(source-over)は次式です。Cd は移動先(背景)の色、Cs は source の色、a はそのフラグメントの不透明度です。
Cout = a * Cs + (1 - a) * Cd
この演算は 非可換 です。半透明の赤(a=0.5)の上に半透明の青を重ねるのと、順序を入れ替えるのとでは、最終色が異なります。したがって物理的に正しい合成には 奥から手前(back-to-front) の描画順が必要で、深度書き込みは止めます(半透明どうしが深度で消し合わないため)。ここまでは透過描画の定石です。
深度ソートの限界
「奥→手前に並べればよい」なら CPU 側でソートすれば済みそうですが、これが厄介です。
物体単位のソートは、2つのメッシュが空間的に貫入する(相互貫入)と破綻します。A の一部は B より手前、別の一部は奥、という状態には単一の前後順が存在しないからです。粒度を三角形単位に細かくしても、互いに交差する三角形や、1つのメッシュ内でリボン・煙・毛が自己重畳する場合、順序は画素ごとに変わり、プリミティブ単位のソートでは一意に決まりません。真に正しい順序は画素ごとに深度で並べ替えるしかない、というのが問題の核心です。
さらにソートには実務的なコストもあります。視点が動くたびに全半透明プリミティブを並べ替える負荷、透明メッシュを1つの描画に束ねられずドローコールが増える問題、そして同じ画素を何度も塗る オーバードロー です。これらを踏まえ、そもそも順序に依存しない合成——順序非依存透過(Order-Independent Transparency, OIT) が求められます。OIT の実装は大きく2系統に分かれます。ソートを近似で回避する 加重ブレンド系 と、全層を溜めて正確に解く A-buffer 系 です。
加重ブレンドOIT(WBOIT)
WBOIT(McGuire と Bavoil, 2013)は発想を反転させます。「正しい順で混ぜる」代わりに、順序に依存しない可換な演算だけで各層を集計 し、最後に一度で合成します。鍵は over 演算子の近似で、各フラグメントを独立に重み付けして 加算 で溜める点です。加算は可換なので、どの順で足しても結果は同じ——ここが順序非依存の源です。
具体的には、レンダーターゲットを2枚使います。ひとつは色を重み付き累積する accumulation、もうひとつは背景の残存率を表す revealage です。各半透明フラグメントについて、深度に依存する重み w(z, a) を掛けて次を足し込みます。
accum += (Cs * a) * w(z, a) // RGBA の RGB に色×α×重み、A に α×重み
revealage *= (1 - a) // 背景がどれだけ透けて残るか(積で溜める)
解決パスでは、蓄えた累積色を重みの総和(accum の alpha)で正規化し、revealage で背景と混ぜ戻します。
avgColor = accum.rgb / max(accum.a, epsilon)
Cout = avgColor * (1 - revealage) + Cbackground * revealage
w は手前の層ほど大きく、奥の層ほど小さくする 深度依存の重み です。これで「手前のものが強く効く」という over 演算子の性質を、ソートなしで近似します。w を深度に無関係な定数にすると全層が均等に混ざり、ただの平均(加算合成)に退化して手前・奥の区別が失われます。逆に急峻すぎると手前の1層だけが支配して背後が飛びます。McGuire らは far/near のレンジやシーンに応じて w を調整する複数の式を提示しており、実務ではシーンに合わせた重みチューニングが品質を左右 します。
WBOIT の長所は明快です。ソート不要、追加バッファ2枚のみ、1パスで書き込める軽さ、そして順序非依存ゆえに前後関係の破綻が起きません。短所は あくまで近似 である点です。重なりが厚い(多数の半透明層が重畳する)と、重み平均のため色が濁り、コントラストが落ちます。色付きガラスを何枚も透かすような濃い重畳では、厳密解との差が目立ちます。
A-buffer/ピクセルリンクリスト方式
厳密さを取るのが A-buffer 系です。各画素に届いた すべての半透明フラグメント(色・α・深度)を捨てずに保存し、後段の解決パスで画素ごとに深度ソートして正しい順に over 合成します。現代の GPU 実装では、可変長の層をどう格納するかが焦点になります。
代表的なのが ピクセルリンクリスト(Per-Pixel Linked List, PPLL) です。DirectX 11 世代以降で使える機能を用い、次の2つのバッファを持ちます。画素ごとに「そのリストの先頭要素の番号」を保持する head ポインタ画像、および全画素分のフラグメントを詰め込む大きな ノードプール(各ノードは色・α・深度・次ノード番号)です。
[書き込みパス] 各半透明フラグメントについて:
idx = atomicCounterIncrement(nodeCount) // プールの空きスロットを1つ確保
node[idx] = { color, alpha, depth,
next = atomicExchange(head[x,y], idx) } // リスト先頭へ差し込む
[解決パス] 各画素について:
head[x,y] からリストを辿り全ノードを収集
深度でソート(層数は画素ごとに可変)
奥→手前に over 合成して最終色を出力
原子的操作(atomic)で先頭に差し込むため書き込み順は不定ですが、解決パスで深度ソートするので 順序非依存かつ厳密 です。層数の上限を設けなければ理論上いくつでも重ねられます。
弱点はメモリと帯域です。ノードプールは「最悪ケースの総フラグメント数」を見積もって確保する必要があり、重畳の深いシーンでは巨大になります。プールを溢れさせると層が失われて描画が壊れるため、上限クリップやタイル分割で運用します。加えて解決パスの画素ごとソートは、層が多い画素で計算がかさみ、レジスタや局所メモリを圧迫します。「正確だが可変長ゆえに最悪コストが読めない」のが本質的な難点です。
もうひとつの古典が 深度ピーリング(depth peeling) です。1パスで最前面の1層だけを剥ぎ取り、次パスでその奥の層、と N パス繰り返して手前から N 層を順に確定します。厳密ですが、必要な層数だけシーンを再描画するため パス数×ジオメトリ負荷 が重く、層数が読めないシーンには不向きです。PPLL は「1回のジオメトリ描画で全層を収集する」点で、この多重描画を避けた発展形と位置づけられます。
性能と正確さのトレードオフ
| 方式 | 正確さ | メモリ/帯域 | 適する状況 |
|---|---|---|---|
| back-to-front ソート | 交差・貫入で破綻 | 低(追加バッファ不要) | 層が薄く前後が明確な少数の半透明 |
| 加重ブレンド(WBOIT) | 近似(厚い重畳で濁る) | 低(固定2バッファ) | パーティクル・煙・多数の薄い半透明 |
| 深度ピーリング | 厳密(層数分の再描画) | 中(層ごとにパス) | 層数が少なく上限が読める場面 |
| A-buffer/PPLL | 厳密(画素ごとソート) | 高(可変長・溢れ注意) | 色付きガラス等の厳密な重畳合成 |
実務では性質で使い分けます。爆発や煙、大量パーティクルのように「薄い半透明が多数重なるが厳密さは要らない」場面は WBOIT が定番で、軽さと破綻のなさが効きます。逆に色付きガラスや UI の重ね合わせなど、色の正確さが最優先で層数が限られる場面は A-buffer が向きます。なお OIT はいずれも半透明パス専用で、不透明物体は従来どおり深度バッファで解くのが前提です。透過を苦手とする ディファードレンダリング では、不透明をディファードで描き半透明だけを OIT で上乗せするハイブリッドが自然な構成になります。
どの OIT を使っても、色の加算・平均・ブレンドはすべて リニア色空間 で行う必要があります。sRGB の非線形符号のまま累積すると、重み付き平均や over 合成が物理的に狂い、半透明の縁が不自然に暗く(または明るく)なります。入力をデガンマしてリニアで集計し、最終出力でエンコードし直すのが鉄則です。
まとめ
- アルファブレンドの over 演算子は 非可換 なので合成が描画順に依存し、正しくは奥→手前のソートが要るが、相互貫入や自己重畳ではプリミティブ単位のソートでも順序が一意に定まらない。
- WBOIT はソートを捨て、色×αと不透明度を深度依存の重みで 加算 集計して1パスで合成する近似。可換ゆえ順序非依存で軽いが、厚い重畳で色が濁る。重み関数の設計が品質を決める。
- A-buffer/PPLL は各画素に全層を溜め、解決パスで深度ソートして厳密に合成する。正確だが層数が可変でメモリと帯域を食い、プール溢れで破綻しうる。深度ピーリングは層ごと再描画する古典的厳密解。
- 選択は性能と正確さの綱引き。多数の薄い半透明は WBOIT、少数の厳密な重畳は A-buffer。いずれも半透明専用で、不透明は深度バッファに任せ、合成はリニア空間で行う。
グラフィックス Article
順序非依存透過(OIT)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
グラフィックス
比較で見る軸
難易度: advanced / カテゴリ: グラフィックス / タグ数: 6
導入後に効く点
加重ブレンドOIT(WBOIT)はソートを捨て、色×αと不透明度の総和を深度依存の重みで累積して1回で合成する近似。加算は可換なので順序非依存で軽いが、重なりが厚いと色が濁る。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- グラフィックス
- タグ数
- 6
判断チェックリスト
- 自社の用途が「グラフィックス / 透過」に近いか確認する。
- 強みである「アルファブレンドは非可換な演算なので合成結果が描画順に依存し、正しくは奥→手前のソートが要る。だがメッシュ内の自己交差や相互貫入では三角形単位のソートでも順序が一意に定まらず破綻する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。